SQL yerleştirme

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:

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.

Kaynaklar