OWASP kategorisi: MASVS-CODE: Kod Kalitesi
Genel Bakış
SQL yerleştirme, kasıtlı olarak açık bırakılan arayüzlerinin ötesinde temel veritabanlarına erişmek için SQL ifadelerine kod ekleyerek güvenlik açığı olan uygulamalardan yararlanır. Saldırı, gizli verileri açığa çıkarabilir, veritabanı içeriklerini bozabilir ve hatta arka uç altyapısını tehlikeye atabilir.
SQL, kullanıcı girişi yürütülmeden önce birleştirilerek dinamik olarak oluşturulan sorgular aracılığıyla saldırıya açık olabilir. Web, mobil ve tüm SQL veritabanı uygulamalarını hedefleyen SQL ekleme, genellikle web güvenlik açıklarının OWASP İlk On listesinde yer alır. Saldırganlar bu tekniği birçok yüksek profilli ihlalde kullandı.
Bu temel örnekte, kullanıcının sipariş numarası kutusuna girdiği ve kaçış karakteri eklenmemiş bir giriş, SQL dizesine eklenebilir ve aşağıdaki sorgu olarak yorumlanabilir:
SELECT * FROM users WHERE email = 'example@example.com' AND order_number = '251542'' LIMIT 1
Bu tür bir kod, web konsolunda bir veritabanı söz dizimi hatası oluşturur. Bu hata, uygulamanın SQL enjeksiyonuna karşı savunmasız olabileceğini gösterir. Sipariş numarasının 'OR 1=1–
ile değiştirilmesi, veritabanı ifadeyi True
olarak değerlendirdiği için kimlik doğrulamanın yapılabilir olduğu anlamına gelir.
Benzer şekilde, bu sorgu bir tablodaki tüm satırları döndürür:
SELECT * FROM purchases WHERE email='admin@app.com' OR 1=1;
İçerik sağlayıcılar
İçerik sağlayıcılar, bir uygulamayla sınırlı tutulabilen veya diğer uygulamalarla paylaşılmak üzere dışa aktarılabilen yapılandırılmış bir depolama mekanizması sunar. İzinler, en az ayrıcalık ilkesine göre ayarlanmalıdır. Dışa aktarılan bir ContentProvider
'te okuma ve yazma için tek bir izin belirtilebilir.
Tüm SQL enjeksiyonlarının kötüye kullanıma yol açmadığını belirtmek isteriz. Bazı içerik sağlayıcılar, okuyuculara SQLite veritabanına tam erişim izni verir. Bu durumda, keyfi sorgu yürütmek pek avantaj sağlamaz. Güvenlik sorununu temsil edebilecek kalıplar şunlardır:
- Tek bir SQLite veritabanı dosyasını paylaşan birden fazla içerik sağlayıcı.
- Bu durumda, her tablo benzersiz bir içerik sağlayıcı için tasarlanmış olabilir. Bir içerik sağlayıcıda başarılı bir SQL yerleştirme, diğer tablolara erişim izni verir.
- İçerik sağlayıcının, aynı veritabanındaki içerikler için birden fazla izni vardır.
- Farklı izin düzeyleriyle erişim izni veren tek bir içerik sağlayıcıda SQL enjeksiyonu, güvenlik veya gizlilik ayarlarının yerel olarak atlanmasına neden olabilir.
Etki
SQL enjeksiyonu, hassas kullanıcı veya uygulama verilerini açığa çıkarabilir, kimlik doğrulama ve yetkilendirme kısıtlamalarını aşabilir ve veritabanlarını bozulmaya veya silinmeye açık hale getirebilir. Kişisel verileri açığa çıkan kullanıcılar için tehlikeli ve kalıcı sonuçlar doğabilir. Uygulama ve hizmet sağlayıcılar, fikri mülkiyetlerini veya kullanıcı güvenini kaybetme riskiyle karşı karşıyadır.
Çözümler
Değiştirilebilir parametreler
Seçim yan tümcelerinde değiştirilebilir parametre olarak ?
ve ayrı bir seçim bağımsız değişkenleri dizisi kullanmak, kullanıcı girişini bir SQL ifadesi parçası olarak yorumlamak yerine doğrudan sorguya bağlar.
Kotlin
// Constructs a selection clause with a replaceable parameter.
val selectionClause = "var = ?"
// Sets up an array of arguments.
val selectionArgs: Array<String> = arrayOf("")
// Adds values to the selection arguments array.
selectionArgs[0] = userInput
Java
// Constructs a selection clause with a replaceable parameter.
String selectionClause = "var = ?";
// Sets up an array of arguments.
String[] selectionArgs = {""};
// Adds values to the selection arguments array.
selectionArgs[0] = userInput;
Kullanıcı girişi, SQL olarak değerlendirilmek yerine doğrudan sorguya bağlanır ve kod yerleştirilmesini önler.
Aşağıda, bir alışveriş uygulamasının, değiştirilebilir parametrelerle satın alma ayrıntılarını almak için gönderdiği sorguyu gösteren daha ayrıntılı bir örnek verilmiştir:
Kotlin
fun validateOrderDetails(email: String, orderNumber: String): Boolean {
val cursor = db.rawQuery(
"select * from purchases where EMAIL = ? and ORDER_NUMBER = ?",
arrayOf(email, orderNumber)
)
val bool = cursor?.moveToFirst() ?: false
cursor?.close()
return bool
}
Java
public boolean validateOrderDetails(String email, String orderNumber) {
boolean bool = false;
Cursor cursor = db.rawQuery(
"select * from purchases where EMAIL = ? and ORDER_NUMBER = ?",
new String[]{email, orderNumber});
if (cursor != null) {
if (cursor.moveToFirst()) {
bool = true;
}
cursor.close();
}
return bool;
}
PreparedStatement nesnelerini kullanma
PreparedStatement
arayüzü, SQL ifadelerini daha sonra birden çok kez verimli bir şekilde yürütülebilecek bir nesne olarak önceden derleyebilir. PreparedStatement, parametreler için yer tutucu olarak ?
kullanır. Bu, aşağıdaki derlenmiş enjeksiyon denemesini etkisiz hale getirir:
WHERE id=295094 OR 1=1;
Bu durumda 295094 OR 1=1
ifadesi, kimlik değeri olarak okunur ve büyük olasılıkla sonuç vermez. Ham sorgu ise OR 1=1
ifadesini WHERE
yan tümcesinin başka bir parçası olarak yorumlar.
Aşağıdaki örnekte parametreli bir sorgu gösterilmektedir:
Kotlin
val pstmt: PreparedStatement = con.prepareStatement(
"UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?").apply {
setString(1, "Barista")
setInt(2, 295094)
}
Java
PreparedStatement pstmt = con.prepareStatement(
"UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?");
pstmt.setString(1, "Barista")
pstmt.setInt(2, 295094)
Sorgu yöntemlerini kullanma
Bu daha uzun örnekte, query()
yönteminin selection
ve selectionArgs
öğeleri bir WHERE
yan tümcesi oluşturmak için birleştirilmiştir. Bağımsız değişkenler ayrı ayrı sağlandığından, birleştirilmeden önce kaçak karakter eklenir ve böylece SQL'in yerleştirilmesi engellenir.
Kotlin
val db: SQLiteDatabase = dbHelper.getReadableDatabase()
// Defines a projection that specifies which columns from the database
// should be selected.
val projection = arrayOf(
BaseColumns._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
)
// Filters results WHERE "title" = 'My Title'.
val selection: String = FeedEntry.COLUMN_NAME_TITLE.toString() + " = ?"
val selectionArgs = arrayOf("My Title")
// Specifies how to sort the results in the returned Cursor object.
val sortOrder: String = FeedEntry.COLUMN_NAME_SUBTITLE.toString() + " DESC"
val cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The array of columns to return
// (pass null to get all)
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // Don't group the rows
null, // Don't filter by row groups
sortOrder // The sort order
).use {
// Perform operations on the query result here.
it.moveToFirst()
}
Java
SQLiteDatabase db = dbHelper.getReadableDatabase();
// Defines a projection that specifies which columns from the database
// should be selected.
String[] projection = {
BaseColumns._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filters results WHERE "title" = 'My Title'.
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// Specifies how to sort the results in the returned Cursor object.
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The array of columns to return (pass null to get all)
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
Düzgün yapılandırılmış SQLiteQueryBuilder kullanın
Geliştiriciler, SQLiteDatabase
nesnelerine gönderilecek sorguları oluşturmaya yardımcı olan bir sınıf olan SQLiteQueryBuilder
'i kullanarak uygulamaları daha da koruyabilir. Önerilen yapılandırmalar şunlardır:
- Sorgu doğrulaması için
setStrict()
modu. setStrictColumns()
, sütunların setProjectionMap'te izin verilenler listesine eklendiğini doğrulamak için kullanılır.- Alt sorguları sınırlamak için
setStrictGrammar()
.
Room kitaplığını kullanma
android.database.sqlite
paketi, Android'de veritabanlarını kullanmak için gerekli API'leri sağlar. Ancak bu yaklaşım, düşük düzeyde kod yazmayı gerektirir ve ham SQL sorgularının derleme zamanında doğrulanmasını sağlamaz. Veri grafikleri değiştikçe etkilenen SQL sorgularının manuel olarak güncellenmesi gerekir. Bu işlem zaman alıcı ve hatalara açık bir süreçtir.
Yüksek düzey bir çözüm, SQLite veritabanları için soyutlama katmanı olarak Room Kalıcılık Kitaplığı'nı kullanmaktır. Odanın özellikleri şunlardır:
- Uygulamanın kalıcı verilerine bağlanmak için ana erişim noktası olarak hizmet veren bir veritabanı sınıfı.
- Veritabanının tablolarını temsil eden veri varlıkları.
- Uygulamanın verileri sorgulamak, güncellemek, eklemek ve silmek için kullanabileceği yöntemleri sağlayan veri erişim nesneleri (DAO'lar).
Odanın avantajları arasında şunlar yer alır:
- SQL sorgularının derleme zamanında doğrulanması.
- Hataya açık ortak metin kodunun azaltılması.
- Veritabanı taşıma işlemi basitleştirilmiştir.
En iyi uygulamalar
SQL yerleştirme, özellikle büyük ve karmaşık uygulamalarda tamamen dayanıklı olmanın zor olabileceği güçlü bir saldırıdır. Veri arayüzlerindeki olası kusurların önemini sınırlamak için aşağıdakiler gibi ek güvenlik önlemleri alınmalıdır:
- Şifreleri şifrelemek için güçlü, tek yönlü ve rastgele karma oluşturma işlemleri:
- Ticari uygulamalar için 256 bit AES.
- Elips biçimli eğri şifreleme için 224 veya 256 bit ortak anahtar boyutları.
- İzinleri sınırlama
- Veri biçimlerini doğru şekilde yapılandırmak ve verilerin beklenen biçime uygun olduğunu doğrulamak.
- Mümkün olduğunda kişisel veya hassas kullanıcı verilerini saklamamak (ör. verileri iletmek veya depolamak yerine karma oluşturma işlemiyle uygulama mantığını uygulamak).
- Hassas verilere erişen API'leri ve üçüncü taraf uygulamalarını en aza indirme.