Ruby on Rails Web Uygulamaları İçin SQL Injection'ı Önleme Teknikleri
Bu makalede, SQL Injection zafiyetine karşı güvenli web uygulamaları geliştirmek isteyen Ruby on Rails geliştiricilerinin kullanabileceği bazı tekniklere yer verilmiştir.
SQL Injection Zafiyeti Nedir?
SQL Injection zararlı verinin, escaping ya da sanitization işlemi uygulanmamış bir halde SQL sorgusuna eklenmesiyle oluşan bir web güvenlik zafiyetidir. Günümüz web uygulamalarında veri tabanlarının önemli bir role sahip olması nedeniyle, SQL sorgularını manipüle edebilmek ve dolayısıyla veri tabanını kontrol edebilmek web uygulamasındaki verileri tehlikeye atmaktadır. Hatta bazı uzaktan kod çalıştırma açıklarına yapılan SQL Injection saldırılarıyla, uygulamanın kontrolünü tümüyle ele geçirmek de mümkün olmaktadır.
SQL Injection’ın ne olduğunu öğrenmek ve SQL Injection cheat sheet vasıtasıyla daha fazla detay edinmek için yazılarımızı okuyabilirsiniz.
Ruby on Rails ile Web Uygulamaları Geliştirirken SQL Injection Oluşmasını Engelleme
Active Record, herhangi bir SQL sorgusu yazmadan veri tabanları arasında etkileşim kurulmasını sağlayan bir Ruby on Rails kütüphanesidir. Veri tabanındaki tablolara gelen karşılıkları haritalandıran bir ORM (Object Relational Mapping) katmanı sağlar.
Veriyi okuma ve manipüle etme için kullanılabilen birçok metot mevcuttur. Bu Active Record metotlarının çoğu her ne kadar parameterized (parametrelerle ifade edilmiş) sorgular kullanıyor ve SQL Injection zafiyetine yol açacağı endişesi olmadan çalıştırılıyor olsa da ham SQL verilerini kabul eden ve dikkatli kullanılmadığı taktirde birtakım SQL Injection zafiyetlerine yol açabilecek bazı metotlar da mevcuttur.
Mümkünse Dinamik Attribute-Based Finders Kullanın
Veri tabanında bilgi aranırken ya da veri tabanından bilgi alınırken SQL Injection açıklarına mahal vermemek için dinamik attribute-based finder’ları kullanmak gerekir. Bunlar, parameterized sorgular olarak çalışır ve geçirilen argümana dikkat eder. Meseleyi bir örnek üzerinden değerlendirelim:
User.find_by(name: params[:name]) # Geleneksel
User.find_by_name(name) # Dynamic Finder
İlk satırda bulunan find_by metodu dikkatli kullanılmadığı taktirde SQL Injection saldırılarına yol açabiliyor. Verilen örnekte, kolon adını ve değerini belirgin bir şekilde göndererek bu durumun böyle olmadığından emin olduk. Dinamik finder’ların kullanıldığı ikinci satırda ise yöntemin kendisini çağırmaktan başka bir şey gerekmiyor. Active Record, argümanın bir kolon ya da bir tablo olmadığını ve uygun escaping işlemiyle SQL sorgusuna eklendiğini bilir.
Sadece Girdilerden Alınan Değerleri Kabul Edin ve Kullanın
INSERT, DELETE, SELECT gibi SQL komutları ve tablo, kolon isimleri gibi SQL sorgularının diğer esas elemanları, girdilerden alınan güvensiz verilerden oluşturulmamalıdır. Bunu gerçekleştirmek için alınan güvensiz girdileri, kolon ve tablo isimlerini ya da ham SQL sorgularını kabul eden Active Record kütüphanesindeki metotlara göndermemek gerekir.
Eğer Dinamik Finder Değilse Bir String’i Asla Bir Argüman Olarak Göndermeyin
Her şeyden önce AR (Active Record) metotlarının neleri argüman olarak beklediğini bilmek çok önemlidir. Bazı metotlar, argümanları çeşitli şekillerde ve farklı formatlarda kabul etmektedir. Girdi, bir integer değeri ise burada herhangi bir sıkıntı yaşanmaz. Fakat bir string söz konusu olursa aynı metot SQL Injection açığını barındırır durumda olacaktır.
Özellikle ActiveRecord::FinderMethods ve ActiveRecord::QueryMethods olmak üzere, Active Record bünyesinde çalışması için harici girdileri zorunlu tutacak olan birçok metot mevcuttur. Kullanıcı girdisi olduğu gibi geçirilmemelidir. Bunun yerine SQL sorgularını değiştirmeyi engelleyen bir şekilde enkapsüle edilmelidir. Bir string, SQL sorgusuna bir ekleme yapma niyetiyle geçirilirse ekseriyetle Active Record olarak yorumlanacaktır.
Argümanlarda tanımlanmış durumlara bağlı olarak filtreleme yapan ActiveRecord::QueryMethods::WhereChain::where metoduna bir göz atalım. Bu metot, string, array veya hash gibi birkaç farklı formattaki durumları kabul eder ve bir SQL ifadesinin WHERE kısmına gider. Şayet basit bir şekilde user_input’u bu metoda gönderirsek alınan veri bir string olduğu için çok basit bir SQL Injection türünü oluşturacak ve bir SQL parçasına sorgu olarak eklenecektir. Böyle bir imkanı eline alan saldırganların elbette mutlu olması kaçınılmazdır. Bunu, bir örnekle açıklayalım:
User.where("name = '#{params[:name]'") # SQL Injection!
name = 'fff' ile talep edilen bu kod satırı, sorguyu şu hale getirecektir:
SELECT "users".* FROM "users" WHERE (name = 'fff')
=> #<ActiveRecord::Relation []>
Fakat "' OR 1='1" değerine set edilirse şöyle olacaktır:
SELECT "users".* FROM "users" WHERE (name = ' ' OR '1'='1')
=> #<ActiveRecord::Relation [#<User id: 1, name:'jack', …….>]>
Yukarıdaki örnekte görüldüğü üzere, OR operatörünün başarılı bir şekilde eklenmesi, veri tabanındaki tüm kayıtları bize döndürecektir. Daha gelişmiş sorgular, elde edilen sonuçları ileri noktalara taşır. Şimdi de kodun, enjeksiyonu engellediği haline bir göz atalım:
User.where(["name = ?", "#{params[:name]}"])
Yukarıdaki kodu kullandığımızda dizinin ilk elemanı bir şablon, harfler ise şablona atanan parametreler olduğundan, SQL Injection oluşması imkansız hale gelecektir.
User.where({ name: params[:name] })
Bu örnek de SQL Injection’a karşı korunaklı bir örnektir. Burada, kolon adı belirgin bir şekilde 'name' olarak belirlenmiş ve kolonun değerini haricen alınan girdi belirlemiştir.