La sicurezza di MongoDB non è completamente garantita configurando semplicemente i certificati di autenticazione o crittografando i dati. Alcuni aggressori "faranno il possibile" giocando con i parametri ricevuti nelle richieste HTTP che vengono utilizzate come parte del processo di query del database.
I database SQL sono i più vulnerabili a questo tipo di attacco, ma l'iniezione esterna è possibile anche nei DBM NoSQL come MongoDB. Nella maggior parte dei casi, le iniezioni esterne si verificano a causa di una concatenazione non sicura di stringhe durante la creazione di query.
Cos'è un attacco di iniezione esterna?
L'iniezione di codice è fondamentalmente l'integrazione di dati non convalidati (vettore non mitigato) in un programma vulnerabile che, una volta eseguito, porta a un accesso disastroso al database; minacciandone l'incolumità.
Quando le variabili non sterilizzate vengono passate in una query MongoDB, rompono la struttura di orientamento della query del documento e talvolta vengono eseguite come il codice javascript stesso. Questo è spesso il caso quando si passano props direttamente dal modulo body-parser per il server Nodejs. Pertanto, un utente malintenzionato può facilmente inserire un oggetto Js dove ti aspetteresti una stringa o un numero, ottenendo così risultati indesiderati o manipolando i tuoi dati.
Considera i dati di seguito nella raccolta di uno studente.
{username:'John Doc', email:'[email protected]', age:20},
{username:'Rafael Silver', email:'[email protected]', age:30},
{username:'Kevin Smith', email:'[email protected]', age:22},
{username:'Pauline Wagu', email:'[email protected]', age:23}
Supponiamo che il tuo programma debba recuperare tutti gli studenti la cui età è pari a 20, scriveresti un codice come questo...
app.get(‘/:age’, function(req, res){
db.collections(“students”).find({age: req.params.age});
})
Avrai inviato un oggetto JSON nella tua richiesta http come
{age: 20}
Questo restituirà tutti gli studenti la cui età è pari a 20 anni come risultato previsto e in questo caso solo {username:'John Doc', email:'[email protected]', age:20} .
Ora supponiamo che un utente malintenzionato invii un oggetto anziché un numero, ad esempio {'$gt:0'};
La query risultante sarà:
db.collections("students").find({età:{'$gt:0'}); che è una query valida che al momento dell'esecuzione restituirà tutti gli studenti in quella raccolta. L'attaccante ha la possibilità di agire sui tuoi dati secondo le sue intenzioni dannose. Nella maggior parte dei casi, un utente malintenzionato inserisce un oggetto personalizzato che contiene comandi MongoDB che consentono loro di accedere ai tuoi documenti senza la procedura corretta.
Alcuni comandi MongoDB eseguono codice Javascript all'interno del motore di database, un potenziale rischio per i tuoi dati. Alcuni di questi comandi sono '$where', '$group' e 'mapReduce'. Per le versioni precedenti a MongoDB 2.4, il codice Js ha accesso all'oggetto db dall'interno della query.
Protezioni native MongoDB
MongoDB utilizza i dati BSON (Binary JSON) sia per le query che per i documenti, ma in alcuni casi può accettare espressioni JSON e Js non serializzate (come quelle menzionate sopra). La maggior parte dei dati passati al server è nel formato di una stringa e può essere inserita direttamente in una query MongoDB. MongoDB non analizza i propri dati, evitando quindi potenziali rischi che possono derivare dall'integrazione di parametri diretti.
Se un'API prevede la codifica di dati in un testo formattato e quel testo deve essere analizzato, ha il potenziale di creare disaccordo tra il chiamante del server e il chiamato del database su come verrà analizzata quella stringa . Se i dati vengono interpretati erroneamente come metadati, lo scenario può potenzialmente rappresentare una minaccia per la sicurezza dei tuoi dati.
Esempi di iniezioni esterne di MongoDB e come gestirle
Consideriamo i dati seguenti in una raccolta di studenti.
{username:'John Doc', password: ‘16djfhg’, email:'[email protected]', age:20},
{username:'Rafael Silver',password: ‘djh’, email:'[email protected]', age:30},
{username:'Kevin Smith', password: ‘16dj’, email:'[email protected]', age:22},
{username:'Pauline Wagu', password: ‘g6yj’, email:'[email protected]', age:23}
Iniezione utilizzando l'operatore $ne (non uguale)
Se voglio restituire il documento con username e password forniti da una richiesta il codice sarà:
app.post('/students, function (req, res) {
var query = {
username: req.body.username,
password: req.body.password
}
db.collection(students).findOne(query, function (err, student) {
res(student);
});
});
Se riceviamo la richiesta di seguito
POST https://localhost/students HTTP/1.1
Content-Type: application/json
{
"username": {"$ne": null},
"password": {"$ne": null}
}
La query restituirà sicuramente il primo studente in questo caso poiché il suo nome utente e password non sono considerati nulli. Questo non è secondo i risultati attesi.
Per risolvere questo problema, puoi usare:
Modulo mongo-sanitize che impedisce a qualsiasi chiave che inizia con '$' di essere passata al motore di query MongoDB.
Installa prima il modulo
npm install mongo-sanitize
var sanitize = require(‘mongo-sanitize’);
var query = {
username: req.body.username,
password: req.body.password
}
Utilizzare mongoose per convalidare i campi dello schema in modo tale che se si aspetta una stringa e riceve un oggetto, la query genererà un errore. Nel nostro caso sopra il valore nullo verrà convertito in una stringa "" che letteralmente non ha alcun impatto.
Iniezione utilizzando l'operatore $where
Questo è uno degli operatori più pericolosi. Consentirà di valutare una stringa all'interno del server stesso. Ad esempio, per recuperare studenti la cui età è superiore a un valore Y, la query sarà
var query = {
$where: “this.age > ”+req.body.age
}
db.collection(students).findOne(query, function (err, student) {
res(student);
});
L'uso del modulo di sanificazione non sarà di aiuto in questo caso se abbiamo uno '0; return true' perché il risultato restituirà tutti gli studenti anziché quelli la cui età è maggiore di un determinato valore. Altre possibili stringhe che puoi ricevere sono "\"; return \ '\' ==\'' o this.email ==='';return '' ==''. Questa query restituirà tutti gli studenti anziché solo quelli che corrispondono alla clausola.
La clausola $where dovrebbe essere fortemente evitata. Oltre alla battuta d'arresto delineata, riduce anche le prestazioni perché non è ottimizzato per utilizzare gli indici.
C'è anche una grande possibilità di passare una funzione nella clausola $where e la variabile non sarà accessibile nell'ambito di MongoDB, quindi potrebbe causare il crash dell'applicazione. Cioè
var query = {
$where: function() {
return this.age > setValue //setValue is not defined
}
}
Puoi anche usare gli operatori $eq, $lt, $lte, $gt, $gte.
Protezione da MongoDB External Injection
Ecco tre cose che puoi fare per proteggerti...
- Convalida i dati dell'utente. Guardando indietro a come l'espressione $where può essere utilizzata per accedere ai tuoi dati, è consigliabile convalidare sempre ciò che gli utenti inviano al tuo server.
- Utilizza il concetto di validatore JSON per convalidare il tuo schema insieme al modulo mongoose.
- Progetta le tue query in modo tale che il codice Js non abbia accesso completo al codice del tuo database.
Conclusione
L'iniezione esterna è possibile anche con MongoDB. È spesso associato a dati utente non convalidati che entrano nelle query MongoDB. È sempre importante rilevare e prevenire l'iniezione di NoSQL testando tutti i dati che potrebbero essere ricevuti dal server. Se trascurato, ciò può minacciare la sicurezza dei dati degli utenti. La procedura più importante è convalidare i tuoi dati a tutti i livelli coinvolti.