10 Ocak 2015

Web Uygulama Denetimi - Bölüm-8: SQL Enjeksiyonu (SQL Injection)

Bilindiği üzere web uygulama teknolojisi pek çok teknoloji katmanından ve entegre çalışan pek çok sistemden oluşmaktadır. Dolayısıyla farklı katmanlarda ve iletişimin farklı aşamalarında farklı dillere yönelik kod enjeksiyon tehditleri bulunmaktadır. Ancak temel açıklık kullanıcı tarafından girilen verinin yetersiz kontrolüdür.Bu dillerden SQL veriye erişim için kullanılan dil olduğu için kuşkusuz tüm diller arasında en çok ilgi çeken saldırı alanıdır.

SQL enjeksiyon teknikleri backend sunucu olarak kullanılan veritabanları için genel olarak benzer olmakla birlikte bazı noktalarda farklılıklar bulunmaktadır. Burada tüm veritabanlarının detayına girmeden genel açıklık denetim yöntemlerinden bahsedeceğiz.

Denetim sırasında karşılaşılabilecek farklı veritabanları için üzerinde çalışmak için aynı veritabanından başka bir kuruluma kendi ortamınızda ulaşabilmek faydalı olacaktır. Eğer böyle bir imkan yoksa interaktif online tutorial’lar sunan “SQLzoo.net” faydalı olabilir.

SQL enjeksiyon açıklıklarını tespit etmek (özellikle hata mesajları özelleştirilmemişse ve detaylı biçimde dönüyorsa) bazen kolay, bazı durumlardaysa açıklığın nasıl kullanılabileceğini veya gerçek bir güvenlik riski doğurup doğurmadığını anlamak zordur.

String Parametrelerine Enjeksiyon


Bir SQL sorgusuna string bir veri tipi yerleştirildiğinde tek tırnak içine yerleştirilir. Böyle bir alana enjeksiyon yapabilmek için bu tek tırnağın kapatılması ve ek kodların enjekte edilmesi gerekir. Buna göre aşağıdaki denetim adımları uygulanabilir:

  • Kullanıcı girdisi olarak (bir HTML form alanına, cookie değerine, başlık değerine, URL parametresine olabilir) tek tırnak girilir ve uygulamanın detaylı hata mesajı verip vermediği veya normal davranışından farklı biçimde davranıp davranmadığı gözlenir.
  • Eğer bir hata veya farklı bir davranışla karşılaşılmışsa 2 tek tırnak gönderilir ve yanıt gözlenir. 2 tek tırnak veritabanları tarafından bir escape karakteri olarak kullanılıp tırnak karakterini gerçek anlamıyla anlaması için kullanılır. Bu durumda hatalı durum ortadan kalkıyorsa web uygulaması büyük ihtimalle SQL enjeksiyon açıklığına sahiptir.
  • Açıklığın doğrulanması için ek olarak string birleştirme karakterleri kullanarak geçerli bir girdi verisi oluşturulabilir. Eğer uygulama bizim oluşturduğumuz veriye verinin normalde girildiği şekilde cevap veriyorsa uygulama SQL enjeksiyon açıklığına sahiptir. Aşağıda farklı veritabanları için gönderilebilecek verilere örnekler bulunmaktadır:

    • Oracle: ‘||’ABC
    • MS-SQL: ‘+’ABC
    • MySQL: ‘ ‘ABC (iki tek tırnak işareti arasında boşluk karakteri bulunmaktadır)

  • Yukarıdaki karakterler girilirken yapılan genel hatalardan biri HTTP protokolünde özel olarak algılanan karakterlerin URL kodlanmamasıdır. Bu kodlamalar değişiklikler parametreler tarayıcı içinden de, proxy’de de yapılıyor olsa doğru biçimde yapılmalıdır. Örneğin:

    • POST verisi içinde & ve = karakterleri sorgu metnini oluştururken parametre adı ve değeri çiftlerini oluşturmak için kullanılır. Bu karakterleri %26 ve %3d olarak kodlamak gereklidir.
    • Sorgu metinleri içinde boşluk karakteri bulunamaz. Eğer bulunursa HTTP sorgu metni o noktada sonlandırılır. Boşluk karakterinin %20 veya + olarak kodlanması gerekir.
    • + karakteri boşluk kodlama amacıyla kullanıldığından gerçekten “artı” işaretinin sorgu içinde bulundurmak için %2b şeklinde kodlanması gerekir.
    • Noktalı virgül (;) cookie’leri ayırmak için kullanıldığından %3b olarak anmalıdır.

Numerik Parametrelere Enjeksiyon


Kullanıcı verisi numerik olsa bile uygulama bu veriyi string veri gibi ele alıyor olabilir, o yüzden önce sayılan adımlar uygulanmalıdır. Ancak bazı durumlarda numerik veri veritabanına numerik olarak gönderilir, bu yüzden tek tırnak içine alınmaz. Bu  nedenle önceki adımlar bir sonuç üretmiyorsa aşağıdaki denetim adımları uygulanabilir:

  • Girdi olarak orjinal numerik değere eşit bir aritmetik işlem girilir. Örneğin 2 girilen alana 1+1 girilir. Eğer uygulama 2 değeri için verdiği yanıtı veriyorsa uygulama açıklık barındırabilir.
  • Ancak bir önceki adımdaki testin açıklığa işaret olabilmesi için değiştirilen verinin uygulama davranışı üzerinde önemli bir etkisi olması gerekir. Örneğin değiştirilen değer numerik bir sayfa numarasıysa değerin doğru veya yanlış birşey ifade etmesi uygulama açısından önemlidir. Ama değiştirilen veri yerine tamamen rastgele bir değer de girsek sonuç değişmiyorsa bu test anlamlı bir sonuç üretmez.
  • Eğer ilk test başarılı ise daha komplike SQL ifadeleri ile ek kanıt aranabilir. Bu ifadelere örnek olarak ASCII fonksiyonu verilebilir. Örneğin ‘A’ karakterinin ASCII değeri 65 olduğundan 2 değerine ulaşmak için şu ifade test edilebilir: 67–ASCII(‘A)
  • Yukarıdaki test tek tırnakların girdi kontrolleri tarafından temizlenmesi durumunda çalışmaz. Bu durumda veritabanlarının implicit olarak numerik veriyi string’e çevirdikleri gerçeğinden yola çıkarak numerik bir değerin karakter olarak ASCII değerini kullanabiliriz. Örneğin ‘1’ karakterinin ASCII değeri 49 olduğundan şu ifade 2 ile sonuçlanacaktır: 51-ASCII(1).
  • Ve yine string paratmetrelerde olduğu gibi URL encoding’e dikkat edilmelidir.

Farklı SQL Cümleleri İçin Enjeksiyon


SQL dilinde SQL cümlelerinin başında farklı yüklemler bulunabilir. Bu yüklemler içerisinde en sık kullanılanı “SELECT” cümlesi olması nedeniyle SQL enjeksiyon açıklıkları daha çok bu ifade türü içinde rastlanır. Ancak diğer yüklemler de SQL enjeksiyon açıklıkları taşıyabilir. Farklı yüklemler için enjeksiyon yöntemleri ve bu yüklemlerin hangi uygulama fonksiyonlarında karşılaşılabileceklerine bakarsak:

SELECT: Sorgu fonksiyonlarında kullanılır. Örneğin kullanıcı profilinizi, alışveriş sepetinizi, hesaplarınızı, vb. görüntülediğiniz isteklerde bu yüklem kullanılır.

Genellikle enjeksiyon noktası WHERE yan cümlesi içinde olduğundan ve bu yan cümle ifadenin sonunda olduğundan ifadenin sorunsuz biçimde tamamlanması ve geri kalanının yorum (comment --) işaretleri ile geçersiz hale getirilmesi kolaydır. Ancak bazen açıklık diğer yan cümleleri (örneğin ORDER BY) ve parametreleri (örneğin diğer tablo veya kolon isimleri) etkileyecek yerlerde olabilir.

INSERT: Yeni bir kayıt üreten fonksiyonlarda kullanılır. Örneğin yeni bir kullanıcı kaydı yapılırken, alışveriş sepetine yeni bir ürün eklerken, yeni bir hesap yaratırken, vb. durumlarda bu yüklem kullanılır.

Insert yüklemine SQL enjeksiyon saldırısı genellikle (kaydın yaratılacağı tablo ve kolon isimleri kullanıcının kontrol edebileceği parametrelerden alınmıyorsa) VALUES yan cümlesinde yer alır. Örneğin yeni bir kullanıcı yaratan şu cümleyi ele alalım:

INSERT INTO users (username, password, ID, privs) VALUES (‘daf’, ‘secret’, 2345, 1)

Eğer yukarıdaki örnekte kullanıcı adı veya password sahalarına SQL enjeksiyon saldırısı yapılabiliyorsa saldırgan ID ve privs sahaları için istediği verileri içeren bir kayıt yaratabilir. Örneğin kendi kullanıcı kodunu sistem yöneticisi anlamına gelen ‘0’ olarak belirleyebilir (tabi ID alanının yerini bildiği varsayımıyla). Ancak tablo alanları ile değerlerin uyumlu olması için INSERT cümlesinin doğru bitirilmesi gereklidir. Bunun için hatalı durum ortadan kalkıncaya kadar birer sayı parametresi ekleyerek denemelere devam edilebilir. Çoğu veritabanı sayıları metin değişkenlere otomatik olarak dönüştürdüğünden bu yaklaşım çalışacaktır. Örneğin aşağıdaki denemeler yapılabilir:

kul’) - -
kul’, 1) - -
kul’, 1, 1) - -
kul’, 1, 1, 1) - -

UPDATE: Bir ya da daha fazla kaydın değiştirilmesi gereken durumlarda kullanılır. Örneğin kullanıcı bilgilerinin değiştirilmesi, alışveriş sepetindeki ürün miktarının değiştirilmesi, hesap bakiyesinin değiştirilmesi gerektiği durumlarda.

UPDATE cümleleri INSERT cümlelerine benzemekle birlikte genellikle WHERE yan cümleleri barındırırlar. Örneğin tipik bir password değiştirme cümlesi aşağıdaki gibi olabilir:

UPDATE users SET password=’yenipassword’ WHERE user=’fatih’ and password=’eskipassword’

Bu sorgu kullanıcının password’ünü değiştirirken aynı zamanda eski password’ü de kontrol etmektedir. Eğer kullanıcı adına SQL enjeksiyon saldırısı yapmak mümkünse aşağıdaki girdi ile admin kullanıcısının password’ü istenen değere getirilebilir:

admin’--

Diğer taraftan SET yan cümlesindeki password sahasına SQL enjeksiyon yapabilmemiz durumunda WHERE yan cümlesini doğru oluşturmamız gerekir. Aksi takdirde tüm kayıtları etkileme riski doğacaktır.

DELETE: Bir tablodan bir veya daha fazla kayıt silmek için kullanılır. Örneğin bir siparişi iptal ettiğimizde, bir para transferini iptal ettiğimizde, vb. bu cümle çağrılabilir. Örnek bir DELETE cümlesi aşağıdaki gibidir:

DELETE FROM sepet WHERE sipariş=’A1234’

UPDATE cümlesinde olduğu gibi genellikle SQL enjeksiyon açıklığı WHERE yan cümlesi içinde bulunur.

SQL Enjeksiyon Riskleri


INSERT, UPDATE ve DELETE cümlelerinin ciddi sonuçları olabileceğinden üzerinde işlem yapılan tablo ve alan isimleri hakkında mümkün olduğunca ön bilgi toplamak ve cümleleri doğru oluşturmakta büyük fayda vardır. Örneğin bir UPDATE cümlesinde WHERE yan cümlesini geçersiz kıldığımızda yapılacak olan değişiklik tablonun tüm kayıtlarında yapılacağından veri bütünlüğüne ciddi zarar verilecektir.

Ayrıca denetim sırasında ne kadar dikkatli olunursa olunsun veritabanı üzerinde ciddi zararlar doğurma riski bulunmaktadır. Bu nedenle tam veritabanı yedeğinin alınması gerekmektedir. Sistem yöneticisi / kurum yöneticisi oluşabilecek risklerin farkında olmalıdır. Denetim ekibiyle riskin kabul edildiğine dair yazılı bir mutabakat yapılması oluşabilecek olumsuz durumlar açısından gereklidir.

UNION Operatörü


UNION operatörü SQL dilinde iki SELECT cümlesinin sonuçlarını birleştirerek sunmak için kullanılır. Sunulan listenin kolon isimleri ilk SELECT cümlesinde atanmış veya ilk SELECT cümlesinin tablo kolon adlarıdır. Bir SELECT cümlesine SQL enjeksiyon yapabiliyorsak UNION operatörü ile veritabanından başka bilgileri de mevcut fonksiyonalite üzerinden (yani rapor ya da liste üreten uygulama fonksiyonunu kullanarak) elde edebiliriz.

UNION Operatörü Kullanarak Bilgi Toplama’da Kısıtlar

Ancak UNION operatörünü hatasız kullanabilmek için aşağıdaki koşulları sağlamak gereklidir:

1.     UNION operatörü ile birleştirilen iki SELECT cümlesinin yapısı aynı olmalıdır. Yani her ikiside aynı sayıda kolona sahip olmalı, aynı sıradaki kolonların veri tipi aynı ya da uyumlu olmalıdır.
2.     Enjeksiyonla eklenecek ikinci sorgunun anlamlı sonuçlar döndürebilmesi için veritabanında bulunan tablo ve kolon isimlerinin bilinmesi gereklidir.

Yukarıdaki koşullardan birincisine odaklanırsak, veritabanı farklı sayıda kolon ve farklı veri tipinde kolonlar sorgulandığında farklı hatalar üretecektir (farklı veritabanları farklı mesajlar üreteceğinden konuyla ilgili bir referansa başvurulabilir).

Bazı uygulamalar arka sistemlerden kaynaklanan hataları kullanıcıya doğrudan görüntülemeyip özel hata sayfaları göstermektedir. Hatanın içeriğini göremediğimiz durumlarda sorgu yapısını tahmin etmek için şu yöntemleri kullanabiliriz:

  • İkinci sorgunun birincisiyle birleştirilebilmesi için mutlaka aynı veri tiplerine sahip olması gerekmez, uyumlu veri tiplerine de sahip olabilir. Numerik veri tipleri veritabanlarında metin veri tipine çevrilir, bu nedenle metin veri tiplerine karşılık da kullanılabilir. Ancak NULL veri tipi tüm veri tiplerine dönüştürülebilir. Bu nedenle veri tipi bilinmeyen kolonlar için SELECT NULL sorgusu kullanılabilir.
  • Veritabanı hataları uygulama tarafından ele alınarak özel hata mesajı ile saklansa bile başarılı olunduğu durumda ek SELECT cümlesinin sonucu hatasız çalışma sonucunda görülür. Bu şekilde doğru sorgunun oluşturulduğunu anlayabiliriz.
  • Çoğu durumda enjeksiyon yapılabilen sorgunun içinde tek bir metin alanı bulmak bizim için yeterli olacaktır. Çünkü, bu alanda istediğimiz sorgu yanıtlarını alıp sistematik olarak veritabanı hakkında bilgi toplayabiliriz.

    UNION operatörünün kullanılabileceği bir SELECT cümlesi SQL enjeksiyon açıklığı bulunduğunda takip eden denetim adımları aşağıdaki gibi olabilir:

    · Öncelikle birinci SELECT cümlesindeki kolon sayısı tespit edilmelidir. Bunun için aşağıdaki aktiviteler gerçekleştirilebilir:

    o   NULL’ın herhangi bir veri tipine dönüştürülebileceğini hatırlarsak aşağıdaki UNION operatörlü enjeksiyonları deneyerek doğru kolon sayısını bulabiliriz. Sorgumuz hatasız biçimde çalıştığında ve NULL kelimesini veya boş metin alanlarını içeren ek bir satır döndüğünde gerekli kolon sayısını tespit edebiliriz:

    §  ‘ UNION SELECT NULL - -
    §  ‘ UNION SELECT NULL, NULL - -
    §  ‘ UNION SELECT NULL, NULL, NULL - -
    §  ...

    o   Orjinal sorguya ORDER BY alt cümlesi eklenerek hata mesajı alınıncaya kadar sıralanacak kolon sırası artırılır. Normalde kolon sayısı aşılmadığı sürece farklı kolonlara göre sıralanmış orjinal sorgu sonuçları görüntülenir. Örnek olarak aşağıdaki enjekte edilen metinler verilebilir:

        • ‘ ORDER BY 1 - -
        • ‘ ORDER BY 2 - -
        • ‘ ORDER BY 3 - -
        • ...

    ·         Kolon sayısı tespit edildikten sonra sıra istediğimiz bilgileri edinebilmek için bir metin veri tipli bir kolon tespit etmektir. Kolon sayısı bilindiği için birinci kolondan başlayarak son kolona kadar her bir NULL ‘a’ karakteriyle (veya başka bir metin karakteriyle) değiştirilerek hata almadan ‘a’ karakterini dönen kolon(lar) tespit edilir. Tespit edilen kolon(lar) daha sonra veritabanından elde edilecek bilgileri toplamak için kullanılabilir.

      • ‘ UNION SELECT ‘a’, NULL, NULL - -
      • ‘ UNION SELECT NULL, ‘a’, NULL - -
      • ‘ UNION SELECT NULL, NULL, ‘a’ - -

    Tek bir metin veri tipli kolon tespit edilse bile bu alan farklı bilgilerin birleştirilmesi şeklinde kullanılabilir. Örneğin UNION operatörü ile Oracle veritabanına yönelik bir sorguda şu şekilde farklı kolon bilgileri bir kolon altında birleştirilebilir:

    login||’:’||password

    MS-SQL’de (%2b olarak encode edilen) + operatörüyle birleştirme (concatenation) işlemi yapılır.

    ·         Örnek bir test olarak tespit edilen bir metin veri tipli alanda veritabanının versiyonu döndürülebilir. Örneğin:

    o   MS-SQL ve MySQL için: ‘ UNION SELECT @@version, NULL, NULL - -
    o   Oracle için: ‘ UNION SELECT banner, NULL, NULL FROM v$version - -
                sorguları kullanılabilir.

    Önemli:  Oracle veritabanında her SELECT cümlesinin FROM özelliği bulunması gerekir. Bu amaçla tablo ismi olarak global olarak erişilebilen DUAL tablosu kullanılabilir. Örneğin:

    ‘ UNION SELECT NULL FROM DUAL - -


    Yukarıdaki örnekte verilen veritabanı versiyon bilgisini edinmek elbette kritik bir bilgi sağlayabilir. Ancak asıl değerli veritabanı bilgilerine ulaşabilmek için “UNION Operatörü Kullanarak Bilgi Toplama’da Kısıtlar” bölümündeki 2. kısıtını aşmak gerekir. Bunun için de hangi tablonun hangi kolonunu sorgulayacağımızı bilmek durumundayız. Tablo ve kolon isimlerini öğrenmek için uygulanabilecek doğrudan ve kör SQL enjeksiyon yöntemleri bir sonraki bölümde detaylı olarak açıklanacaktır.



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