web for pentester 2 ile web uygulama güvenligine giris

18
WEB FOR PENTESTER II ile WEB UYGULAMA GÜVENLİĞİNE GİRİŞ Umut ERGİN [email protected] Web for pentester II uygulaması www.pentesterlab.com tarafından geliştirilmiş olan ve çeşitli zafiyetler barındıran bir web uygulamasıdır.

Upload: umut-ergin

Post on 11-Apr-2017

309 views

Category:

Technology


7 download

TRANSCRIPT

WEB FOR PENTESTER II ile WEBUYGULAMA GÜVENLİĞİNE GİRİŞ

Umut ERGİ[email protected]

Web for pentester II uygulaması www.pentesterlab.com tarafındangeliştirilmiş olan ve çeşitli zafiyetler barındıran bir web uygulamasıdır.

SQL INJECTION

Birinci örneğimizde karşımıza basit bir login sayfası gelmekte. Web for Pentester I'in çözümünde kullandığımız OR '1'='1' mantığını, sık kullanılan bir kullanıcı adı(admin) ile denediğimizde başarıyla giriş yapabilmekteyiz. Dilediğimiz takdirde username kısmında da bir takım SQL sorguları verilerek benzer sonuçlara erişilip, veritabanı üzerinde manipülasyonlar yapılabilir.

İkinci örnekte ise aynı payload kullanıldığında başarısız olmaktayız. Ancakgiriş adı olarak admin' gibi bir değer verdiğimizde hata alıyoruz. Buradan da giriş adının bulunduğu sorgudan sonra bir takım kontrol yapıldığı kanısına varabiliriz. Bu kontrolleri geçersiz kılmak için LIMIT 1 yazıp sonuna da # ifadesini koyduğumuzda, sonrasında gelen komutları geçersiz saydırmış olacağız.

Üçüncü örnekte ise tek tırnak ifadesi engellenmiş. Payload'ımızı belirlemek için arkada çalışan SQL sorgusunu tahmin etmeye çalışalım.

SELECT * FROM users WHERE username='[kullaniciadi]' ANDpassword='[parola]'

Teorik olarak, bir tırnaktan kurtulabilmek için, sorguya başka bir tırnak eklememiz gerek. Ancak \ karakterini kullanarak da tırnak içindeki ifadeden kurtulabilmemiz mümkün. Kullanıcı adı tarafına \ yazdığımız takdirde oluşacak sorgu şu şekilde görünecek.

SELECT * FROM users WHERE username='\' AND password=' '

Burada \ işareti kendisinden sonra gelen tırnak normal bir karakter gibi algılanacak ve username parametresi ' AND password= olarak kalacak. Parola kısmına da or 1=1 # yerleştirerek mantıksal olarak doğru bir cevap döndürülecek ve bu şekilde başarılı bir şekilde giriş yapmış olacağız.

Dördüncü örnekte karşımızda herhangi bir textbox yok, URL'deexample4/?req=username%3d'hacker' gibi bir ifade görmekteyiz. %3d ifadesi eşittir anlamına gelmekte. URL'i tekrar gözden geçirelim.

example4/?req=username='hacker'

username=' ifadesi verdiğimizde tüm sorguyu görmekteyiz. Burada da daha önce kullandığımız ' ' or 1=1 # ifadesini kullanabiliriz.

Beşinci örnekte URL'de Limit ifadesi verilmiş. Limit ifadesini komple URL'den silerek diğer kullanıcılara da ulaşabiliyoruz.

Altıncı örnekte de aynı şekilde bir zafiyet var. URL'in sonuna union “all select * from users” ekleyerek ya da direkt olarak group=username ifadesini silerek diğer kullanıcılara ulaşabilmekteyiz.

Yedinci örnekte URL'de id=1 ifadesini görüyoruz. Tek tırnak ifadesi ekleyerek durum hakkında biraz daha bilgi sahibi olabiliriz. Gördüğümüz sorgudan da anlayabiliyoruz ki, altıncı örnekte kullandığımız metotlarla yine diğer kullanıcılara ulaşabilmekteyiz.

Sekizinci örnekte ise kullanıcı adı ve parola ile üyelik oluşturabilmekteyiz. İlk aşamada özel karakterler ile kullanıcı adı oluşturabiliyoruz gibi gözükse de, bu koruma sadece kullanıcı oluşturulurken devreye sokulmuş. Ancak veritabanından okurken böyle bir koruma bulunmamakta. İlk önce normal bir kullanıcı oluşturuyoruz ve arkasından da kullanıcı adına payloadımızı vereceğimiz bir kullanıcı oluşturuyoruz.

ID’si 6 olan kullanıcımıza tıkladığımızda ise, id’si 5 olan kullanıcının bilgilerini görebilmekteyiz. Başka bir kullanıcıyı görmek istediğimizde de kullanıcı id’sinin farklı olduğu bir üyelik oluşturmamız yeterli olacaktır.

Dokuzuncu örnekte ise biraz daha özel bir durum var. SQL Injection zafiyetinden kaçınmak için mysql_real_escape_string() adında bir Javascript fonksiyonu kullanılmış. Ancak çince karakterlerde bu fonksiyon işe yaramamakta. Buna göre kullanacağımız payload;

呵' or 1=1 #

CAPTCHA

Bu bölümde dizayn/kodlama kaynaklı hatalar sayesinde captcha baypas etmeye çalışacağız.

Birinci örnekte captcha alanına rastgele bir değer verip gönderilen isteği Burp ile incelediğimizde URL’de captcha=girilen_değer olarak gönderildiğini görüyoruz.

Captcha değerinin doğru olup olmadığı kontrol edilmesi ön planda tutulmuş ancak Captcha değerinin varlığı kontrol edilmemiş. Verdiğimiz URL’de captcha parametresini sildiğimiz takdirde doğru değeri girmeden ilerleyebilmekteyiz.

İkinci örnekte ise cevap aslında sayfanın HTML kodunda saklı. Doğru değer direkt olarak sayfada saklanıyor.

Üçünü örnek de ikinci örneğe biraz benzer, yine uygulamanın kendisindenedindiğimiz bilgiler ile captcha’yı baypas edebiliyoruz. Ancak bu sefer captcha değeri sayfanın kaynak kodunda değil, cookie değerinde saklanmış.

Dördüncü örneği baypas edebilmemiz için bir seferliğine doğru captcha’yıgirmiş olmamız gerek. Giriş yaptıktan sonra bize gelen cevapta bir session id atanması yapıldığını görüyoruz.

Bu session id ve captcha değerleri işimize yarayacak, bu yüzden bu değerleri not almamızda fayda var. Not aldıktan sonra tarayıcımızı kapatıp tekrar captcha örneğine giriş yapıyoruz. Bu durumda doğal olarak karşımıza farklı bir captcha geliyor.

Yine bir istek gönderip Burp’te inceleyelim.

Şimdi captcha değerine daha önce giriş yapmış olduğumuz ‘watson’ değerini ve session id’ye de ‘watson’ ile girdiğimizde bize atanmış olan session id değerini verelim.

Beşinci örnekte teknik bir sıkıntı görünmese de, birkaç deneme yaptıktan sonra kullanılan kelimelerin az ve belirli olduklarını farkedebilmekteyiz. Bu işlemi otomatize edebilmek için, kelimeleri ve kelimeleri barındıran resimlerin MD5 hash’lerini kıyaslayarak doğru captcha değerini bulan bir script yazılabilir.

Altıncı örnekte ise tesseract isimli OCR(Optical Character Recognition) aracını kullanacağız. Debian tabanlı işletim sistemlerinde aşağıdaki komut ile tesseract’ı yükleyebilirsiniz.

$ sudo apt-get install tesseract-ocr

Aracımızı yükledikten sonra örneğimize geri dönebiliriz. Benim karşıma gelen captcha şu şekilde;

Karşımıza gelen resmi indiriyoruz ve tesseract ile çalıştırıyoruz.

Dilendiği takdirde bu işlemler ruby veya başka bir dil aracılığı ile bir scriptyazılıp otomatize edilebilir.

Yedinci örnekte ise captcha kodunun arkaplanında çizgiler bulunmakta ve bu çizgiler tesseract’ın karakter tanıması yapmasını zorlaştırmakta. Tesseract ile okumayı denediğimiz takdirde başarılı bir sonuç alamamaktayız, bunun için kelimeyi, arkaplandaki çizgilerden arındırabilmemiz gerek. Ben bu işlem için ruby dilinde bulunan ‘rmagick’ adında bir gem kullanacağım.

require 'rmagick'image = Magick::Image.read("captcha.png").firstimage = image.threshold(5)image.write("captcha.png")

Threshold değerini artırıp azaltarak arkaplandaki gürültüye göre ayarlayabilirsiniz. Ben bu örnekte 5 değerini kullanacağım. Captchanın işlemden önceki hali;

Sonraki hali;

Bu resim üzerinden tesseract’ı kullandığımızda ise;

Sekizinci örnekte de durum benzer, yine arkaplanımızda çizgiler var ancak bu sefer kelime orta kısmından biraz içeri girmiş gibi görünüyor. Bunun için de scriptimize ilgili methodu ekleyeceğiz.

image = image.implode(1)

Captcha’mızın ilk hali;

Threshold ve implode değerlerinde yaptığım denemeler sonrası son hali;

Daha da belirginleştirmek için değerleri deneyebilirsiniz, ben bu seferlik tesseract’ın tanıyabileceği kadarı ile yetindim.

Dokuzuncu örnekte ise captcha HTML içinde bir aritmetik işlem olarak verilmiş. Sayfayı parse edip aritmetik işlemi yapabilecek ufak bir script yazarak bu captcha’yı da baypas edebiliyoruz.

AUTHENTICATION

Bu bölümde ise basitten karmaşığa doğru kimlik doğrulama aşamalarındaortaya çıkabilecek zafiyet örneklerinden bahsedeceğim.

Birinci örneğe girdiğimizde karşımıza bir giriş paneli gelmekte. Oldukça sık kullanılan bir kullanıcı adı/şifre çifti olan admin/admin ile bu örneği hızlıca geçebiliyoruz.

İkinci örnekte ise brute force yöntemi kullanarak karakter karşılaştırmalarında geçen zamana göre parolaya ulaşacağız. Örneğe girdiğimizde karşımıza bir login ekranı gelmekte ve kullanıcı adının ‘hacker’ olduğunu görmekteyiz, bizdense parolayı bulmamız isteniyor. Dilerseniz bu işlem için bir script yazabilirsiniz, ben hazırda var olan stabil bir brute force aracını kullanacağım. Patator isimli bu araca aşağıdaki linkten ulaşabilirsiniz.

https://github.com/lanjelot/patator

Şimdi ilk olarak patator aracını çalıştırıp, paroladaki ilk karakterleri deneyip, isteklere yapılan dönüşler için geçen zamanları inceleyeceğiz. Bunu yapmamızın sebebi ise doğru karakteri bulduğumuz takdirde yapılacak olan ikinci karakter kontrolünün, ilk karakterin yanlış olduğu kontrole göre daha uzunsürecek olması. Örneğin diyelim ki parolamız ‘ABC’, bu durumda işlem şu şekilde ilerleyecektir;

• A harfi denenecek, doğru olduğu için bir sonraki karaktere geçilecek, bu durumda geçen süre iki karakterin kontrolü için gereken süreye tekabül edecek.

• B harfi denenecek, yanlış olduğu için bir sonraki karakter denenmeden çıkılacak, bu durumda geçen süre bir karakterin kontrolü için geçen süreye tekabül edecek.

• C harfi içinde B harfinin aynısı geçerli olacak.

Bu durumda da A harfi için kontrol edilen süre, B ve C harflerinden daha uzun olacağı için, A harfinin parolamızın ilk karakteri olduğunu anlayabiliriz.

Resimde de görüldüğü üzere P harfi için geçen süre diğerlerinden daha uzun, bu da bize parolanın ilk harfinin p olduğunu söylüyor. Bu işlemleri devam ettirerek 200 kodlu bir cevap aldığımız takdirde parolaya ulaşmış oluyoruz.

Üçüncü örnekte user1 olarak giriş yapabiliyoruz, ancak bizden admin olarak giriş yapılmamız isteniyor. Bu örnek için göndereceğimiz HTTP isteğini incelememiz gerek, ben bu işlem için Burp Suite aracını kullanacağım.

Burp aracılığı ile isteğe baktığımız zaman, cookie değerinin user1 olduğunu görüyoruz. Kullanıcı adımız da user1 idi, cookie değerimiz de user1. Bu değeri Repeater modülüne gönderip admin yapmayı deneyelim.

Dördüncü örnekte de benzer bir durum söz konusu, ancak isteği gözlemlediğimiz zaman cookie değerinin 32 karakterden oluşan bir string olduğunu görmekteyiz. Buradan da cookie değerinin MD5 algoritması ile hashlendiğini söyleyebiliriz. Cookie değerini herhangi bir MD5 çözücü kullanarak açıyoruz.

Cookie değerimizin MD5 ile hashlenmiş user1 olduğunu görmüş olduk. Yine admin değerini MD5 ile hashleyip cookie değerine verdiğimiz takdirde başarılı bir şekilde giriş yapmış olacağız.

Beşinci örnekte ilk kullanıcının bize admin olduğu söylenmiş. Alt kısımda ise bir kayıt ol(register) bölümü bulunmakta. Register kısmında admin kullanıcı adı ile kayıt olmaya çalıştığımızda bu isimde başka bir kullanıcının olduğu hatasını almaktayız. Burada önemli olan nokta; kullanıcının varolup olmadığı kontrolü bir progralama dili ile(Ruby, Python gibi) kontrol edilirken, kullanıcı adı ve şifre kontrolü MySQL ile yapılmakta. Default MySQL ayarlarında büyük-küçükharf kontrolü yapılmamakta olduğu için “Admin” isimli bir kullanıcı açıp kaydedebilmekteyiz. Bu şekilde yeni oluşturduğumuz kullanıcı admin’in yerine geçip bu aşamadaki kimlik doğrulamayı bypass edebiliyoruz.

Altıncı örnekte ise beşinci örnekteki durumun aynısı bulunmakta, ancak bu sefer büyük küçük harf kontrolü yapılmış bu yüzden aynı şekilde admin olarak kayıt olamıyoruz. Çözüm olarak MySQL’in string’leri karşılaştırma kriterlerine bakmamız gerek. Default olarak MySQL kullanımında boşluklar gözardı edilir, yani ‘umutergin’ ile ‘ umutergin’ aynı anlama gelmektedir. Bu özelliği kullanarak beşinci örnekteki gibi bu aşamayı da geçebilmekteyiz.

AUTHORIZATION

Türkçe’ye yetkilendirme olarak geçen authorization kelimesi ile ifade edilen zafiyetler, uygulamada kullanılan mantığa göre oldukça sık karşılaşılan zafiyetlerdir. Yetkilendirme zafiyetleri otomatik araçlar ile test edilemediği için, manuel olarak test edilmesi önemlidir. Framework’ler injection saldırılarına karşıönlem alabiliyorken, yetkilendirme zafiyetlerine karşı otomatik olarak önlemler alamamaktadırlar.

Birinci örnekte giriş yaptıktan sonra iki adet dosyayı görebilme yetisi kazanıyoruz. Ancak logout olduktan sonra bile, dosyanın URL’ini girdiğimizde yine dosyayı görebilmekteyiz. Bu da demek oluyor ki URL bir kere öğrenildikten sonra erişim konusunda bir sıkıntı bulunmamakta.

İkinci örnekte ise yine giriş yaptıktan sonra karşımıza iki adet dosya gelmekte. URL’i incelediğimiz zaman, dosyaların nümerik olarak sınıflandırıldığını görüyoruz. Gerekli kontroller yapılmadığı için, URL’deki sayıları

değiştirerek, erişmememiz gereken dosyalara erişebilmekteyiz.(user2)

Üçüncü örnekte ikinci örneğin aynısı. Ancak burada direkt olarak erişim sağlayamıyoruz, bunun yerine biz edit yetkisi verilmiş. Editlerken yine URL’de yapacağımız değişiklik ile user2’nin dosyalarını okuyabilmekteyiz.

MASS-ASSIGNMENT

Veritabanı ile ilgili işlemler için SQL sorgularının kullanıldığını biliyoruz. Ancak web uygulamalarında kullanılan sorguların sayısı ve karmaşıklığı zamanlaarttığı için manüel olarak bu sorguların yazılması zaman içinde uzun ve yorucu bir iş haline geldi. Bu problemi ortadan kaldırmak için ORM(Object Relational Mapping) gibi teknolojiler kullanılmaya başlandı. Bu teknolojileri, veritabanı ile nesneye dayalı programlama arasındaki köprüler olarak özetleyebiliriz. Örnek vermek gerekirse;

$kullanici = User::findById(123);

Yukarıdaki kod sayesinde SQL sorgusu ile uğraşmadan ID’si 123 olan Userbulunup kullanici nesnesine atanabilmekte. Bu durum mem yazım hem de okuma konusunda kolaylık sağlasa da, güvenlik açısından problemler oluşturabilmekte.

$kullanici = new User(array('username' => 'Umut','password' => 'cis' ));

Burada ise kullanıcı adı Umut ve parolası cis olan yeni bir kullanıcı oluşturmaktayız. Oluşturduğumuz yeni kullanıcının tüm niteliklerini bu şekilde girebilmek oldukça kolay. Ancak geliştirici yeni oluşturulacak olan kullanıcının özelliklerini doğru şekilde korumadığı takdirde aşağıdaki gibi bir kullanıcı oluşturulup, istenmeyen sonuçlar elde edilebilir.

$kullanici = new User(array('username' => 'Umut','password' => 'parola',

'IsAdmin' => true ));

Birinci örnekte karşımıza bir register ekranı gelmekte ve bizden admin niteliğine sahip olarak bir kullanıcı ile kayıt olmamız istenmiş. Burp ile isteği yakalayıp inceleyelim.

Burada user kullanıcısına ait username ve password niteliklerini görebilmekteyiz. Yukarıda bahsettiğimiz diğer admin özelliğini de isteğimize ekleyelim.

İkinci örnekte ise aynı durum var, ancak bu sefer direkt admin niteliğinde bir üyelik oluşturulmasına izin verilmemiş. Giriş yaptığımızda ise kullanıcı bilgilerimizi değiştirmemizi sağlayan “modify your account” kısmında birinci örnekteki işlemlerin aynılarını yaptığımız da yine admin niteliklerine sahip bir kullanıcıya sahip olmuş oluyoruz.

Üçüncü örnekte ise user1:pentester giriş bilgileri ile giriş yapabiliyoruz ve karşımıza Company 1 isimli şirketin bilgileri gelmekte ve bizden Company 2 isimli şirketin bilgilerini görmemiz istenmekte. Bunu yapabilmek için mass-assignment ile Company 1 bilgilerini değiştirebiliyor olmamız gerek. İlk iki örnekte yaptığımız gibi company_id adında bir nitelik tanımlayıp, gerekli id numarasını verdiğimiz takdirde, Company 2’nin bilgilerine ulaşabilmekteyiz.

RANDOMNESS ISSUES

Programlama yaparken, rastgele değer üretmenin birçok yolu bulunmaktadır. Kullanılan bu yollara bağlı olarak ürettiğiniz değerin rastgeleliği değişmektedir. Bu değerler üretilirken manuel olarak ‘seed’ değerinin girilmesi sonucunda güvenlik problemleri ortaya çıkabilmektedir. Seçilen seed değeri, rastgele seçilecek olan değerin başlangıç noktasını belirleyeceği için, atanılan değerin rastgeleliği de değişecektir.

Birinci örnekte bir Ruby script’i verilmiş ve bunun yanında ikinci kullanıcının adı ‘hacker’ parolası ise ‘hjtvse’ olarak verilmiş. Parola ataması için kullanılan scripti incelediğimizde aynı seed değerinin(0) kullanılması sonucu ikinci kullanıcının ‘hjtvse’ parolasını aldığını görmekteyiz. Bu scripti çalıştırarak birinci kullanıcı olan admin’in parolasına ulaşabiliriz.

Gördüğümüz gibi ikinci şifre hacker kullanıcısına ait. Birinci şifreyi admin kullanıcısı için deneyelim.

İkinci örnekte de rastgele değer üretimi yapılırken anlık zaman değeri seed olarak alınmış. Ben uygulamayı açtığımda kullanıcı adı ‘hacker’ parolası ise ‘jxrbmt’ olan bir kullanıcı oluşturulmuş.(Bu değer zamana bağlı olarak üretildiği için siz açtığınızda farklı olabilir.) Bunun için ufak bir brute forceyöntemi kullanabiliriz. Uygulamanın parolayı oluşturduğu zamana dönene kadartek tek geri saydırabilirsiniz. Script’in oluşturduğu ikinci değer ‘jxrbmt’ -ya da sizin aldığınız değer olduğunda- olduğunda, alacağınız ilk değer admin’in parolası olacaktır. Başka bir yöntem olarak uygulama parolayı oluştururken Ruby scriptini çalıştırabilirsiniz, birkaç kere denediğiniz zaman hacker kullanıcısının parolasını görebilirsiniz.

Bu parolayı admin kullanıcısı için deneyelim.

Üçüncü örnekte birinci örneğe çok benzer, tek farkı burada parolanın biraz daha meşakatli bir şekilde oluşturulması. Birinci örnekteki işlemleri gerçekleştirerek yine admin kullanıcısının parolasına ulaşabilmekteyiz.

Dördüncü örnekte de benzer bir durum söz konusu ancak s.rand fonksiyonu yerine rand fonksiyonu kullanıldığı için daha öncesinde kaç defa random üretecinin çağrıldığını bilmediğimiz için brute-force yapacağız. Bunun için ufak bi script yazıp, değerlerini kaydettikten sonra hacker kullanıcı adını parolasını bulmamız, bize admin’in parolasını da verecektir.(Ben çalıştırdığımda hacker’ın parolası sodurs idi, sizde farklı olacaktır.)

Aradığımız değeri bulmuş olduk. Denediğimizde admin olarak giriş yapabilmekteyiz.

MONGODB INJECTION

MongoDB NoSQL kullanan bir veritabanı türüdür. Bu saldırıların mantığı SQL injection ile birebir aynı olmakla beraber tek farkı SQL yerine NoSQL kullanılmasıdır.

Örnekte bir giriş formuyla karşı karşıyayız. SQL injectionda kullandığımız 1=1 true mantığını burada NoSQL hali ile uygulayabiliriz. SQL injectionda olduğu gibi ‘ işaretini kullandığımızda bir compile error almaktayız. Buradan da sorgunun arka planda tamamlanmadığını anlayabiliyoruz. Sorguyu ‘ ile kapattıktan sonra || 1=1 ile true döndürüp, // ile de sorgunun kalan kısmını yorum satırı haline getirip, payload’ımızı verebiliriz.