Makale Özeti

ASP sonrasında ASP.NET gibi güçlü bir programlama tekniği, SQL Server gibi güçlü bir veritabanı altyapısıyla uygulama geliştiricilerin yapabildikleri, bundan 5-6 yıl öncesine göre oldukça değişti ve kolaylaştı. Ancak bu güç, bazı küçük noktaları gözden kaçırmamız halinde (özellikle veritabanı işlemlerini kullanıcıdan veri alarak gerçekleştiriyorsak) son derece kolay bir şekilde bize karşı kullanılabilir. Evet konumuz SQL Injection..

Makale

Aikidoda size ilk söylenen şeylerden biri gücünüzün rakibinizin fiziksel gücüyle doğru orantılı olduğunuzdur. Gerçektende doğrudur. Rakibinizin gücünü ona karşı kullanırsınız.
ASP sonrasında ASP.NET gibi güçlü bir programlama tekniği, SQL Server gibi güçlü bir veritabanı altyapısıyla uygulama geliştiricilerin yapabildikleri, bundan 5-6 yıl öncesine göre oldukça değişti ve kolaylaştı. Ancak bu güç, bazı küçük noktaları gözden kaçırmamız halinde (özellikle veritabanı işlemlerini kullanıcıdan veri alarak gerçekleştiriyorsak) son derece kolay bir şekilde bize karşı kullanılabilir. Evet konumuz SQL Injection :)

SQL Injection tekniğinin altındaki düşünce şudur; Kullanıcıların bilgi gireceği bir metin kutusu içeren bir web sayfası hazırladınız ve kullanıcının girdiği veriye göre bir veritabanı sorgusu gerçekleştireceksiniz. Kötü niyetli bir kullanıcı metin kutusuna mantıksal olarak geçerli, yani çalıştırılabilecek bir ifade girerek SQL sorgumuzun istenilenden farklı bir şekilde çalışmasına neden oluyor. Hatta bazı durumlarda o veritabanı üzerinde çalışan son sorgu da olabilir bu. Bu nasıl oluyor da oluyor sorusuna cevap verelim..

Veritabanı
İlk olarak makale boyunca kullanacağımız örnekler için veritabanımızı hazırlayalım. Ben northwind veritabanı içinde Users adlı bir tablo oluşturdum. Tablonun yapısı ve içindeki örnek kayıtlar şu şekilde;

Şekil 1: Veritabanımızın Yapısı Şekil 2: Örnek Kayıtlar

Amazon.comdan Nasdaq.coma pek çok ASP.NET uygulamasında aşağıdaki örnekte gördüğünüze benzeyen bir üyelik mekanizması kullanılmaktadır. Kullanıcı Giriş butonunu tıkladığında cmdLogin_Click metodu çalıştırılarak kullanıcının girdiği verileri veritabanında ilgili alanlarla karşılaştırarak doğruluğu denetlenir, eğer geriye dönen satır sayısı sıfırdan fazla ise kullanıcının belirttiği bilgilerle eşleşen bir kullanıcı tanımlı olduğundan kullanıcının kimliği doğrulanmış olur.

Çoğu durumda form arzu edildiği gibi ve sorunsuz olarak çalışır ve kullanıcıya giriş başarılı veya başarısız sonucunu gösterir.

Şekil 3: Kullanıcı Giriş Sayfası Şekil 4: Giriş Butonu tıklandığında çalıştırılan kod


Ancak bir gün gelir ve meraklı (kötü niyetli demiyorum, sadece merak :) ) bir kullanıcı gelir ve Kullanıcı adı ve parola alanlarına aşağıdaki ifadeyi yazar;

Şekil 5: Meraklı kullanıcımızın girdiği kullanıcı adı ve parola.. Şekil 6: Ve sonucu..

Peki bu nasıl oldu?
Meraklı kullanıcımız metin kutularına girdiği değerle SQL Sorgusunun geçerliliğini bozmadığı için bir hata ile karşılaşmadı.
Sorgumuzu hatırlayalım;

string strQry = "SELECT Count(*) FROM Users WHERE UserName=" +
txtUser.Text + " AND Password=" + txtPassword.Text + "";

Kullanıcı kullanıcı adı olarak "kadirs" ve parola olarak "kadirs" girdiğinde strQry şu şekli alıyor:

string strQry = "SELECT Count(*) FROM Users WHERE UserName=kadirs and Password = kadirs

Ancak meraklı kullanıcının girdiği değer

OR 1=1 --

Bu durumda strQry şu şekli alıyor:

string strQry = "SELECT Count(*) FROM Users WHERE UserName= OR 1=1

1=1 ifadesi tablodaki her kayıt için geçerlidir dolayısıyla hangi ifade ile birlikte kullanılırsa kullanılsın döneceği sonuç her zaman true olacaktır. Bu nedenle tabloda en az bir kayıt olduğu sürece bu sorgu mutlaka sıfırdan yüksek bir değer döndürecektir. Oluşturduğumuz tabloda 4 örnek kayıt eklemiştik. Kullanıcı OR 1=1 -- ifadesini girdiğinde oluşan sorguyu çalıştırdığımızda dönen sonucu görmemiz konuyu daha kolay anlamamızı sağlayacaktır.

Şekil 7:
Kullanıcı OR 1=1 -- ifadesini girdiğinde oluşan sorguyu çalıştırdığımızda tablodaki kayıt sayısı geri dönüyor.

SQL Injection sadece form authentication uygulamalarında ortaya çıkan bir açık değil. Meraklı kullanıcımızın SQL dili, veritabanı yapısı konularındaki bilgisi ve ne kadar meraklı olduğuyla bağlı olarak dinamik olarak oluşturulan ve kullanıcıdan alınan bilgilerin kullanıldığı hemen hemen tüm sql sorgularında söz konusu olabilmektedir.
Aşağıdaki tasarım ve kodlarla urunlistesi.aspx dosyasını hazırladığımızı varsayalım..

Şekil 8: Ürün Listesi Sayfası
Şekil 9: Ürün Listesi Sayfasının Kodu

Sayfamız Northwind veritabanındaki ürünler tablosundaki ürünleri listelemekte ve kullanıcıların metin kutusunu kullanarak görüntülenen ürünleri filtrelemesine izin vermektedir. Her meraklı kullanıcının hayalini hazırladık ve sunuyoruz :)
Bu biraz öncekine göre daha riskli bir örnek. Kullanıcının bu örnekteki açığı kullanarak veritabanımızdaki verileri değiştirmesi hatta silmesi ve hatta sql server üzerinde yeni kullanıcı hesapları açması çok çok daha kolay. SQL server gibi gelişmiş veritabanı yönetim sistemlerinin çoğu, metadatayı sistem tablolarında saklar. Bu, meraklı kullanıcımızın ona verdiğimiz bu açığı kullanarak çok daha ileri gidip aşağıdaki sorguyu çalıştırarak aşağıdaki sonucu görüntülemesini ve daha ileriye gitmek için bir adım atmasının hiçte zor olmadığı anlamına geliyor..

UNION SELECT id, name, , 0 FROM sysobjects WHERE xtype =U --

Bu ifadenin çalıştırılması sonucunda meraklı kullanıcımız aşağıdaki listeyi görüyor:

Şekil 10: Northwind.. Evet :)

UNION ifadesi bu gibi meraklı kullanıcılar açısından son derece faydalıdır. Bu ifade sayesinde bir sorgunun sonucunu diğer bir sorguya ekleyebiliyoruz. Bu örnekte meraklı kullanıcımız Products tablosuna ait orjinal sorgunun sonuna veritabanındaki kullanıcı tablolarını ekledi. Sorgu SQL Server açısından mantıksal olarak hala geçerli ve çalıştırılabilir. Sonucu yukarıda görebiliyoruz.

UNION ifadesinin önemine biraz daha dikkat çekmek faydalı olacaktır. Peki sorgumuz Products tablosundan verileri almak amacıyla yazılmış. UNION ifadesini kullanarak bu sorgunun yapabileceklerini biraz arttıralım:

UNION SELECT 0, UserName, Password, 0 FROM Users --

Ve bu sorguyu çalıştıralım;

Şekil 11: Products tablosu iftiharla sunar; Users tablosu ve içeriği..

SQL Injection veritabanımızdaki verileri değiştirmek veya veritabanına zarar vermek içinde kullanılabilir. Örneğin bir online alışveriş siteniz var ve meraklı bir kullanıcınızın 18 usd fiyatla sattığınız bir ürün için 18 usd verme imkanı yok. Şu sorguya ne dersiniz?

; UPDATE Products SET UnitPrice = 0.01 WHERE ProductId = 1--

Ve artık ürünü satın alabilir..

Şekil 12: ProductID si 1 olan ürün 18 USD ancak malesef kullanıcımızın bu kadar parası yok
Şekil 13: Kullanıcımız bu sorguyu çalıştırıyor ve..
Şekil 14: Artık ürünü satın alabilir.

Meraklı kullanıcımız veritabanımızdaki bütün tabloları görüntüledi, tüm kullanıcı hesaplarını listeledi, bir ürünümüzün fiyatını 18 USD den 0.01 USDye düşürdü. Çok geç kalmadan bu kullanıcıya dur dememiz gerekiyor artık.

Kim durduracak bu meraklıyı?..
Öncelikle bu sorunun sadece Microsoft SQL Servera özgü bir sorun olmadığını bilmek gerekiyor (yani oracle, mysql, sybase vb. kullanmamız güvende olduğumuz anlamına gelmiyor). Bu sorun SQL Serverın yapısından değil SQL dilinden kaynaklanmaktadır.. SQL dilini güçlü kılan bazı özelliklerinden dolayı bu ve benzeri bazı açıklara sahip..
- Bir çift tek tırnak kullanarak sql ifadesine yorumları ekleyebilmek
- Birden çok SQL ifadesini bir arada yazmak ve bir batch olarak çalıştırabilmek
- Sistem tablolarındaki metadatayı sorgulayabilmek
gibi özellikler..

Genellikle veritabanı tarafından desteklenen dil güçlendikçe, veritabanı saldırılara daha açık ve korunmasız bir hal alıyor. Bu nedenle SQL Serverın SQL Injection ve benzeri saldırılar için popüler bir hedef olması şaşırtıcı gelmiyor.

SQL Injection saldırıları sadece ASP.NET uygulamaları için geçerli bir tehdit değil. ASP, Java, Jsp ve PHP uygulamalarıda ASP.NET uygulamaları ile aynı ölçüde risk altında. Sadece web uygulamaları değil, windows uygulamalarıda SQL Injection karşısında aynı ölçüde saldırıya açık durumda.

Basit bir fonksiyonla SQL Injection saldırılarına karşı önlem alabilmemiz mümkün olsa da böyle bir önlem aşılması basit bir tedbirden öteye geçemeyecektir. Bu nedenle katmanlı bir yaklaşımla önlem almak en doğrusu olacaktır. Bu şekilde önlemlerimizden biri aşılsa bile meraklı kullanıcımızın aşması gereken katmanlar henüz bitmemiş olacaktır. Bu katmanlar nelerdir? Aşağıdaki tabloda inceleyelim..

Kural Uygulaması
1) Kullanıcı girişlerine asla güvenmeyin Metin kutusu vb. kontrollerden gelecek kullanıcı verilerinin tamamını validation kontrolleri, regular expression ifadeleri, ek kod vb. yöntemlerle pek çok defa denetleyin. Bir kullanıcı girişini en az iki defa kontrol etmek için harcayacağınız zaman ile kazanacağınız zaman harcadığınızdan çok daha fazla olacaktır. (denetimi yapmaz ve bu nedenle işinizden olursanız sahip olacağınız zamanı düşünün :) )
2) Dinamik SQL ifadeleri kaçının Parameterized SQL veya Stored Procedure leri kullanın..
3) Asla veritabanına Admin seviyesinde bir account ile bağlanmayın Gerekiyorsa her uygulama için ayrı bir sql accountu oluşturun veya uygulamalar için ortak bir account oluşturun ve bu accounta sadece ilgili veritabanları üzerinde gereken yetkileri verin. SA hesabını kullanmak kolayınıza gelecektir mutlaka ama yapmayın.
4) Gizli verileri düz metin olarak tutmayın Parola, kredi kartı bilgileri vb bilgileri düz metin olarak saklamayın. Mutlaka şifreleyin. Aynı zamanda connectionstring leri de şifreleyin.
5) Exceptionlar mümkün olduğunca az bilgi içersin. Exception olduğunda;
şu sunucudaki şu veritabanına şu kullanıcı adı ve şu parola ile bağlanırken bir sorun oldu kullanıcı hesabında bir sorun olabilir şeklinde bir exception mesajı makalemizdeki örnekte bahsettiğimiz meraklı kullanıcılar için harika olacaktır ama siz onları çok düşünmeyin. Hata oldu. en ideal hata mesajıdır.. Tanımlanmamış hataların oluşması durumunda en az bilgi görüntülemek için customErrorsu kullanın. Debug = False ta güzel bir önlem olacaktır.

1) Kullanıcı girişlerine asla güvenmeyin
Tabloda yer alan kurallardan özellikle birincisi çok önemlidir. Tüm kullanıcı girişlerini bir numaralı düşman olarak görmeliyiz. Kullanıcı girişlerini mutlaka, defalarca denetlemeliyiz. Asp.net validation kontrolleri ve regular expressions bu işin çok yetenekli iki araçtır.

Doğrulama konusuna iki temel yaklaşım vardır. Birincisi sıkıntıya neden olabilecek bütün karakterlerin girilmesine izin vermek. Basitçe bazı temel karakterlerin kullanımını engelleyebileceğiniz gibi - veya gibi karakterlerin kullanılamması sıklıkla soruna neden olabilir daha da kötüsü meraklı kullanıcımız için faydalı olabilecek bir karakteri gözden kaçırabilirsiniz. Bu yöntemde geliştirilebilecek en iyi çözüm, kullanımının soruna neden olmayacağından emin olunan karakterlerin belirlenmesi ve kullanılmalarına izin verilmesi olabilir. Bu yöntem oldukça fazla çalışmayı gerektirecek ancak oldukça güvenli bir yöntem olacaktır. Hangi yöntemi seçtiğinizden bağımsız olarak girilebilecek karakter sayısını gereği kadarıyla sınırlamak her durumda doğru bir seçim olacaktır.

Çoğu durumda kullanıcının gibi tehlikeli olabilecek karakterleri kullanmasına izin vermemiz gerekecektir. Bu gibi durumlarda tek tırnak işaretini iki tane tek tırnak ile değiştirebilirsiniz. Örneğin;

string denetlenmisstring = strInput.Replace("", "");

2) Dinamik SQL ifadeleri kaçının
Bu makalede gördüğümüz SQL Injection sorunları dinamik SQL ifadelerinin kullanılmasından yani kullanıcıdan gelen veriler kullanılarak oluşturulan SQL ifadelerinin çalıştırılmasından kaynaklanıyordu. Parameterized SQL kullanımı SQL Injection konusunda çok ciddi bir önlem olacaktır.

private void cmdLogin_Click(object sender, EventArgs e)
	{
	string strCnx = ConfigurationSettings.AppSettings["cnxNWindBad"];
	using (SqlConnection cnx = new SqlConnection(strCnx))
	{
	SqlParameter prm;cnx.Open();
	string strQry =
	"SELECT Count(*) FROM Users WHERE UserName=@username AND Password=@password";
	int intRecs;
	SqlCommand cmd = new SqlCommand(strQry, cnx); cmd.CommandType = CommandType.Text;
	prm = new SqlParameter("@username", SqlDbType.VarChar, 50); 
        prm.Direction = ParameterDirection.Input; prm.Value = txtUser.Text; cmd.Parameters.Add(prm);
	prm = new SqlParameter("@password", SqlDbType.VarChar, 50); 
        prm.Direction = ParameterDirection.Input; prm.Value = txtPassword.Text; cmd.Parameters.Add(prm);
	intRecs = (int)cmd.ExecuteScalar();
	if (intRecs > 0)
	{ lblMsg.Text = "giriş başarısız."; }
	else
	{ lblMsg.Text = "giriş başarılı."; } } }
Şekil 15: Parameterized SQL.. Daha doğru bir giriş sayfası

Yukarıdaki örnek daha doğru bir giriş sayfası örneğini gösteriyor.
Stored Procedureleri kullanmaya yanaşmayan bir IT departmanınız varsa veya kullandığınız veritabanı yönetim sistemi Stored Procedureleri desteklemiyorsa (MySQL < v5.0 gibi) parameterized sql doğru bir önlem olacaktır ancak mümkün olan her durumda Stored Procedureler en doğru seçim olacaktır.

private void cmdLogin_Click(object sender, EventArgs e) {
    string strCnx = 
        ConfigurationSettings.AppSettings["cnxNWindBetter"];
    using (SqlConnection cnx = new SqlConnection(strCnx))
    {
        SqlParameter prm;
        cnx.Open();
        string strAccessLevel;
        SqlCommand cmd = new SqlCommand("procVerifyUser", cnx);
        cmd.CommandType= CommandType.StoredProcedure;
        prm = new SqlParameter("@username",SqlDbType.VarChar,50); 
        prm.Direction=ParameterDirection.Input; prm.Value = txtUser.Text; cmd.Parameters.Add(prm);
        prm = new SqlParameter("@password",SqlDbType.VarChar,50); 
        prm.Direction=ParameterDirection.Input; prm.Value = txtPassword.Text; cmd.Parameters.Add(prm);            

        strAccessLevel = (string) cmd.ExecuteScalar();

        if (strAccessLevel.Length>0) {
            lblMsg.Text = "giriş başarılı.";
        }
        else { lblMsg.Text = "giriş başarısız."; }}}
Şekil 16: Stored Procedureler.. Çok Daha doğru bir giriş sayfası

3) Asla veritabanına Admin seviyesinde bir account ile bağlanmayın (Mümkün olan en düşük yetki ile çalışın)
SQL Injection sorununu yaşadığımız ilk iki örnekteki hatalardan biride kodların sa kullanıcı hesabı ile çalışıyor olmasıydı. Bu da kaçınmamız gereken uygulamalardan bir diğeri. Son iki örneğimizde kullandığımız connectionstring satırı web.config dosyasından geliyor ve şu şekilde.

sa accountu ile çalışmamızın sakıncası nerede? sa kullanıcı hesabı System Administrator rolünde çalışır, yani her türlü işlemi gerçekleştirme yetkisine sahiptir. Bu durumda uygulamamızdan bir drop database komutu alınması durumunda kullanıcının bu işlemi tapma yetkisi olduğundan komut çalıştırılacaktır. Ancak her iş için o işe özel bir account kullanılması durumunda (örneğin okuma işlemleri için datareader ve denydatawriter hakları verilmiş bir account gibi) kullanılan account bu derece riskli bir komutu çalıştırma yetkisi olmayacağından sorun yaşanmayacaktır.

NWindReader kullanıcı hesabı db_datareader ve db_denydatawriter hakları ile çalışmakta. Dolayısıyla bu account ile yapılan sorgulamalarla oluşturulan web sayfalarından gelecek zararlı insert, update ve delete gibi komutlar çalıştırılamayacaktır.

4) Gizli verileri düz metin olarak tutmayın
Şekil 3te gördüğümüz SQL Injection saldırısı sonucunda Users tablosundaki tüm veriler görüntülenmiştir. Bu gibi saldırılarda ikinci bir gvenlik katmanı olarak parolaları şifrelenmiş veya hashed olarak saklamak doğru bir tercih olacaktır. Hashed parolalar decrypt edilemeyeceklerinden şifrelenmiş parolalara göre çok daha güvenlidir. hashed bir parolayı, hash değere biraz tuz ekleyerek (salt - şifrelemede güvenli random bir sayı) daha güvenli hale getirebilirsiniz.

private void cmdLogin_Click(object sender, System.EventArgs e) {
    try {
        // şifrelenmiş connectionstring i al ve decrypt et.
        string strCnx = SecureConnection.GetCnxString("cnxNWindBest");
        // bağlantıyı oluştur
        using (SqlConnection cnx = new SqlConnection(strCnx))
        { SqlParameter prm; cnx.Open();
            // SP kullanarak kullanıcıya ait hashed parolayı al
            string strHashedDbPwd;
            SqlCommand cmd = new SqlCommand("procGetHashedPassword", cnx); 
            cmd.CommandType = CommandType.StoredProcedure; 
            prm = new SqlParameter("@username", SqlDbType.VarChar,50); 
            prm.Direction = ParameterDirection.Input; prm.Value = txtUser.Text; cmd.Parameters.Add(prm); 
            strHashedDbPwd = (string) cmd.ExecuteScalar();
            if (strHashedDbPwd.Length>0) {
            // hashed ve kullanıcının girdiği parola aynı mı denetle..
            if (SaltedHash.ValidatePassword(txtPassword.Text,  strHashedDbPwd)) { lblMsg.Text = "giriş başarılı.."; }
            else { lblMsg.Text = "Login attempt failed."; }}
            else { lblMsg.Text = "Giriş başarısız.."; } } }
    catch { lblMsg.Text = "Giriş başarısız.."; } }
Şekil 17: Hashed bilgiler, diğer önlemler.. En doğru bir giriş sayfası

Örnekte farkettiğiniz ancak henüz açıklamadığımız SaltedHash adlı bir sınıf mevcut. Bu sınıf System.Security.Cryptography alan adı içindeki FormsAuthentication.HashPasswordForStoringConfigFile metodunu kullanarak parola hashleri oluşturuyor ve System.Security.Cryptography alan adı içindeki RNGCryptoServiceProvider.GetNonZeroBytes metodunu kullanarak Convert.ToBase64String metodu ile stringe dönüştürüldüğünde 24 karakterlik bir metin oluşturan 16-byte lık bir salt değer oluşturuyor.

using System;
using System.Web.Security;
using System.Security.Cryptography;
public class SaltedHash
{
	static public bool ValidatePassword(string password, string saltedHash)
	{
		// Extract hash and salt string
		const int LEN = 24;
		string saltString = saltedHash.Substring(saltedHash.Length - LEN);
		string hash1 = saltedHash.Substring(0, saltedHash.Length - LEN);
		// Append the salt string to the password
		string saltedPassword = password + saltString;
		// Hash the salted password
		string hash2 =
			FormsAuthentication.HashPasswordForStoringInConfigFile(
			saltedPassword, "SHA1");
		// Compare the hashes
		return (hash1.CompareTo(hash2) == 0);
	}
	static public string CreateSaltedPasswordHash(string password)
	{
		// Generate random salt string
		RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
		byte[] saltBytes = new byte[16];
		csp.GetNonZeroBytes(saltBytes);
		string saltString = Convert.ToBase64String(saltBytes);
		// Append the salt string to the password
		string saltedPassword = password + saltString;
		// Hash the salted password
		string hash =
			FormsAuthentication.HashPasswordForStoringInConfigFile(
			saltedPassword, "SHA1");
		// Append the salt to the hash
		return hash + saltString;
	}
}
Şekil 18: Jeff Prosise tarafından geliştirilen SaltedHash sınıfı



SQL Injection saldırıları ile doğrudan ilişkili olmasa da, güvenlik açısından doğru bir uygulamayı daha görüyoruz; connectionstringin şifrelenmesi. ConnectionStringin bir parola içermesi gibi durumlarda bu bilginin şifrelenmiş olarak saklanması doğru bir uygulama olacaktır. Örneğimizin son halinde web.config içinde şifrelenmiş olarak saklanan connectionstringin nasıl göründüğüne bakalım:

Meraklı kullanıcımız açısından pek tatmin edici olmadığı kesin.. Yine örneğimizde kullanılan bir diğer sınıf ise SecureConnection sınıfı. Bu sınıf ile cnxNWindBest AppSetting değeri alınarak aşağıdaki kod ile decrypt ediliyor.

string strCnx = SecureConnection.GetCnxString("cnxNWindBest");

SecureConnection sınıfının kodları ise şöyle;

public class SecureConnection {
    static public string GetCnxString(string configKey) {
        string strCnx;
        try {
            // web.config dosyasından şifrelenmiş connectionstring i oku
            string strEncryptedCnx = ConfigurationSettings.AppSettings[configKey];
            // connectionstring i decrypt et
            DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE); 
            byte[] dataToDecrypt =  Convert.FromBase64String(strEncryptedCnx);
            strCnx = Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null)); }
        catch { strCnx=""; }
	return strCnx;
} }
Şekil 19: SecureConnection Sınıfı

SecureConnection sınıfı çağrıları Win32 Data Protection APIye (DAPI) aktaran DataProtect sınıf kütüphanesine çağrılarda bulunmaktadır. DAPInin güzel özelliklerinden biri şifreleme anahtarını sizin için yönetmesi.DAPI ile ilgili detaylı bilgi ve ne zaman kullanmanız gerektiği konusunda açıklamaları "Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication" başlıklı makalede bulabilirsiniz.

5) Exceptionlar mümkün olduğunca az bilgi içersin.
Bu kadar önlemi almamıza, kod yazmamıza rağmen atlayacağımız küçük bir parametre bütün bu çabanın boşa gitmesine neden olabilir.
Tahmin edilebilen veya edilemeyen tüm hatalar için önceden uyarı mesajları veya yapılacak işlemler belirlenmelidir. Kullanıcıya gösterilecek hata mesajı mümkün olduğunca az bilgi içermelidir. Hata olduğunu bilsin, kusura bakmayın diyelim, varsa bir önerimiz onnu yazalım.. Hepsi bu.. Fazlasına ne gerek var, ne de doğru..

Handle etmediğimiz exceptionlara karşı Debug özelliğini False olarak ayarladığımızdan ve customErrors özelliğini On veya RemoteOnly olarak ayarladığımızdan (web.config dosyasında) emin olalım..

RemoteOnly ifadesi ile siteye localhosttan erişen kullanıcıların hata mesajları hakkında detaylı bilgi almasını ancak diğer kullanıcıların sadece hata ile ilgili özet bilgi almasını, On özelliği local kullanıcılarında hata ile ilgili özet bilgi almasını sağlar. Bu özelliği asla Off olarak ayarlamamak önemlidir.

Sonuç...
SQL Injection günümüz web ve windows uygulamaları açısından çok önemli bir risk. Meraklı veya kötü niyetli kullanıcıların uygulamayı geliştiren kişilerin sahip olduğundan çok daha az SQL bilgisi ile verileri görüntüleyebilmesi, değiştirmesi veya silmesi son derece kolay. Ancak bu tehditten korunmakta aynı ölçüde kolay. Yapmamız gereken sadece olasılıkları dikkate almak ve güvenlik kodu yazmak için gerekli zamanı ayırmak.

Özetlemek gerekirse, önemli verileri şifrelemek, kullanıcılardan gelecek tüm verileri defalarca doğrulamak, veritabanı bağlantılarını mümkün olan en düşük yetkilendirmeyle gerçekleştirmek, kullanıcılara exceptionlar konusunda fazla bilgi vermemek, en önemlisi bunların en az bir kaçını aynı anda uygulamak, yani birden çok katmandan oluşan bir güvenlik mekanizması oluşturmak SQL Injection riski karşısında uygulamalarımızın güvenliğini sağlamak için yeterli olacaktır.