SQL yerleştirme

OWASP kategorisi: MASVS-CODE: Kod Kalitesi

Genel Bakış

SQL yerleştirme, temel veritabanlarına kasıtlı olarak kullanıma sunulan arayüzlerin ötesinde erişmek için SQL ifadelerine kod yerleştirerek güvenlik açığı olan uygulamalardan yararlanır. Bu saldırı, özel verileri açığa çıkarabilir, veritabanı içeriklerini bozabilir ve hatta arka uç altyapısının güvenliğini tehlikeye atabilir.

SQL, yürütülmeden önce kullanıcı girişi birleştirilerek dinamik olarak oluşturulan sorgular aracılığıyla saldırıya açık olabilir. Web'i, mobil cihazları ve tüm SQL veritabanı uygulamalarını hedefleyen SQL ekleme, genellikle web güvenlik açıklarıyla ilgili OWASP Top Ten listesinde yer alır. Saldırganlar, bu tekniği birkaç önemli veri ihlalinde kullandı.

Bu temel örnekte, bir kullanıcının sipariş numarası kutusuna girdiği çıkış karakteri eklenmemiş 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 veritabanı söz dizimi hatası oluşturur. Bu da uygulamanın SQL eklemeye karşı savunmasız olabileceğini gösterir. Sipariş numarasının yerine 'OR 1=1– yazıldığında, veritabanı ifadeyi True olarak değerlendirdiğinden (çünkü bir her zaman bire eşittir) kimlik doğrulama gerçekleştirilebilir.

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ı olabilen 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, okuma ve yazma için tek bir izin içerebilir.

Tüm SQL eklemelerinin kötüye kullanıma yol açmadığını belirtmek gerekir. Bazı içerik sağlayıcılar, okuyuculara SQLite veritabanına tam erişim izni verir. Bu durumda, rastgele sorgu yürütme özelliği 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 işlemi, diğer tüm tablolara erişim izni verirdi.
  • Bir içerik sağlayıcı, aynı veritabanındaki içerikler için birden fazla izne sahip.
    • Farklı izin düzeyleriyle erişim sağlayan tek bir içerik sağlayıcıdaki SQL ekleme, güvenlik veya gizlilik ayarlarının yerel olarak atlanmasına neden olabilir.

Etki

SQL ekleme, 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 karşı savunmasız bırakabilir. Etkiler arasında, kişisel verileri ifşa edilen kullanıcılar için tehlikeli ve kalıcı sonuçlar yer alabilir. Uygulama ve hizmet sağlayıcılar, fikri mülkiyetlerini veya kullanıcıların güvenini kaybetme riskiyle karşı karşıya kalır.

Çözümler

Değiştirilebilir parametreler

Seçme ifadelerinde değiştirilebilir parametre olarak ? ve ayrı bir seçim bağımsız değişkenleri dizisi kullanmak, kullanıcı girişini SQL ifadesinin bir 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ğlanarak kod yerleştirme işlemini engeller.

Aşağıda, satın alma ayrıntılarını almak için alışveriş uygulamasının sorgusunu gösteren ve değiştirilebilir parametreler içeren 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 bir nesne olarak önceden derler. Bu nesne daha sonra birden çok kez verimli bir şekilde yürütülebilir. PreparedStatement, parametreler için yer tutucu olarak ? kullanır. Bu da aşağıdaki derlenmiş ekleme girişimini etkisiz hale getirir:

WHERE id=295094 OR 1=1;

Bu durumda, 295094 OR 1=1 ifadesi kimlik değeri olarak okunur ve muhtemelen sonuç vermez. Ancak ham bir sorgu, OR 1=1 ifadesini WHERE ifadesinin başka bir bölümü 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 bölümleri WHERE ifadesini oluşturmak için birleştirilmiştir. Bağımsız değişkenler ayrı ayrı sağlandığından birleştirilmeden önce kaçış karakteri eklenir. Bu sayede SQL injection önlenir.

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
    );

Doğru şekilde yapılandırılmış SQLiteQueryBuilder kullanın

Geliştiriciler, SQLiteQueryBuilder sınıfını kullanarak uygulamaları daha fazla koruyabilir. Bu sınıf, SQLiteDatabase nesnelerine gönderilecek sorguların oluşturulmasına yardımcı olur. Ö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üzeyli kod yazmayı gerektirir ve ham SQL sorgularının derleme zamanı doğrulaması yoktur. Veri grafikleri değiştiğinde etkilenen SQL sorgularının manuel olarak güncellenmesi gerekir. Bu işlem zaman alır ve hata yapma olasılığı yüksektir.

Üst düzey bir çözüm, SQLite veritabanları için soyutlama katmanı olarak Room Persistence Library'yi 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öntemler sağlayan veri erişimi nesneleri (DAO'lar).

Room'un avantajları:

  • SQL sorgularının derleme zamanında doğrulanması.
  • Hata yapmaya yatkın ortak metin kodunun azaltılması.
  • Veritabanı taşıma işlemini kolaylaştırın.

En iyi uygulamalar

SQL yerleştirme, özellikle büyük ve karmaşık uygulamalarda tamamen dirençli olmanın zor olabileceği güçlü bir saldırıdır. Aşağıdakiler de dahil olmak üzere veri arayüzlerindeki olası kusurların ciddiyetini sınırlamak için ek güvenlik önlemleri alınmalıdır:

  • Şifreleri şifrelemek için güçlü, tek yönlü ve rastgele hash değerleri:
    • 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 hassas bir şekilde yapılandırma ve verilerin beklenen biçime uygun olduğunu doğrulama.
  • Mümkün olduğunda kişisel veya hassas kullanıcı verilerini depolamaktan kaçının (ör. verileri iletmek veya depolamak yerine karma oluşturma yoluyla uygulama mantığı uygulayın).
  • Hassas verilere erişen API'leri ve üçüncü taraf uygulamalarını en aza indirme

Kaynaklar