This is the first of a series of articles exploring various topics that may be of interest to my LinkedIn network. As my experience has been diverse, the topics of my articles will also be varied.
There will be an initial set of articles on penetration testing, with a focus on web applications, JavaScript, injection attacks, and databases. This first article will focus on MongoDB and NoSQL Injection.
Planned future articles will cover topics such as: alternative application-layer security topics; additional penetration testing focussed topics; security tokens; risk modelling & analysis; pitfalls of cryptography; smart-card based authentication; security protocols; and much more.
Who is this article for?
Understanding application layer security threats is important for a wide-range of professions, including:
Security Architects and Solution Architects, as they need to understand the potential risks and mitigations to take appropriate design decisions, such as access control.
Software Engineers / Developers, because they need to understand how to build their applications in a secure manner and avoid critical mistakes.
Security Consultants of various types as they need to understand the risks of what they may be reviewing or consulting on.
Penetration Testers who need to understand how to attack these systems!
MongoDB is a No-SQL Database, based around BSON Documents and can be queried just like a regular SQL Database, however, the query language is different to that of traditional SQL Databases.
To understand this article, it is useful to have a basic understanding of No-SQL Databases and SQL Injection attacks. On Firesand's website, we have two primer articles on these topics:
No-SQL Injection Attacks in MongoDB
MongoDB on their public website, within their FAQ confidently claim that 'traditional SQL Injection attacks are not a problem':
As a client program assembles a query in MongoDB, it builds a BSON object, not a string. Thus traditional SQL injection attacks are not a problem. More details and some nuances are covered below [2].
This has been taken by security professionals, researchers, and hackers alike as a bit of a challenge. Needless to say, there are now a wide array of common techniques.
Another key point is, that whilst the MongoDB APIs / Class libraries offer the ability to construct queries piece-by-piece as BSON Documents, they also allow for a textual JSON query to be supplied - so, in this article, we shall start there.
Basic No-SQL Query
To extract documents (the MongoDB, closest, equivalent to a traditional database record or row), you can utilise code such as the following C#.NET example:
var authNQuery = "{username: 'Yossarian', password: 'Catch22'}";
var success = db.GetCollection<BsonDocument("Auth").Find(authNQuery).First() != null;
Using the MongoDB.Driver classes, the IMongoDatabase Interface defines a method for retrieving an instance of a class that implements the IMongoCollection Interface.
It is from classes implementing this Interface where the bulk of database operations will occur, as the Interface exposes various CRUD (Create, Read, Update, Delete) methods, e.g. IMongoCollection.Find as per the sample code above.
The above code sample will seamlessly typecast the JSON string into a FilterDefinition, which is a function that returns a BSON Document. The 'Find()' method itself ultimately returns a BSON Document. When the 'First()' method is called, it will execute the query that is represented by that BSON Document.
The following is the JSON Document that is used to query the MongoDB Collection:
{ username: 'Yossarian', password: 'Catch22' }
However, MongoDB also offers other ways of expressing the above, using more sophisticated Filter query documents that can contain various MongoDB operators that are parsed, and executed, by the MongoDB. Such as the following:
{ username: {'$eq' : 'Yossarian'}, password: {'$eq' : 'Yossarian'} }
There are a wide array of MongoDB operators, some of which are listed below:
$eq: equal to [4]
$ne: not equal to [8]
$lt: less than [9]
$gt: greater than [11]
$lte: less than, or equal to [10]
$gte: greater than, or equal to [12]
and many others...
There are also logical operators such as:
$or: logical OR [6]
$not: logical NOT [3]
$and: logical AND [7]
And other powerful commands, such as:
$where: the ability to execute a filter function written in JavaScript [5]
$lookup: the ability to access other Collections [13]
and many others...
Authentication Bypass Using No-SQL Injection
Now we have a deeper understanding of the types of queries and operators we can use, we shall start by presenting a No-SQL Injection version of the SQL Injection attack shown in the SQL Injection primer article. As such, the scenario will be very similar, again we have a user, an application that constructs a query (this time a BSON/JSON Document), and a database (this time, MongoDB):
If we assume we have a record - or document - with the following data, stored in a MongoDB authentication collection:
{
id : '1',
username : 'Yossarian',
password : 'Catch22'
}
Then, let’s assume that there is a C#.NET method that queries the authentication data to perform end-user authentication, that looks like the following:
private static bool AuthenticateUser(IMongoDatabase db, string username, string password)
{
// Build JSON Query (vulnerable method!)
var authNQuery = "{username: '" + username + "', password: '" + password + "'}";
// Execute Query, returning user document if username and password
// match, else not authenticated if null return.
return db.GetCollection<BsonDocument("Auth").Find(authNQuery).First() != null;
}
Due to the way that the code is building a JSON query, without validating the input variables, it is susceptible to injection. However, injecting into these two values whilst maintaining a legitimate query is not trivial. The query that we can inject into, looks like:
{ username: '<username>', password: '<password>' }
First, we shall tackle the username property. If we start with the account we want to crack, which we shall suppose is the account named Yossarian. If we simply provide that account name, and an empty password we would end up with a constructed query like the following:
{ username: 'Yossarian', password: '' }
Executing this query will not return the desired record, this is because there is no document that has both a username of Yossarian and an empty password. This is literally logging in, by attempting to just supply a username!
As mentioned above, MongoDB supports the $not operator, if we can find a way to inject this, we can construct a query that looks for a document where the username is Yossarian and the password is not empty.
The $not operator works as per the following [3]:
{field: $not: { <operator-expression> } }
Using this we can inject a longer expression into the username, such as:
Yossarian', { password: $not: { { $eq: '' } } }
$eq is the operator for equals, as such this query looks for a document, where the username is Yossarian and the password is not empty.
Entering the above into a login prompt, would result in the following final query:
{username: 'Yossarian', {password: $not: {{$eq: '' }}}', password: ''}
This query will not work yet, there is at least one issue with syntax (i.e., a spurious single quote), and it is still checking for an account with an empty password. Unfortunately, comments, unlike in traditional SQL Injection attacks, are not usable.
However, there are two different types of quotes, which offers a way of nullifying the trailing quote, and the second use of the password field.
If we thus expand the injection into the username field, as there is another field in the documents (id), we can utilise this, but checking that the id is not the non-sensical value of ', password: ', by injecting the following into the username:
Yossarian', {password: $not: {{$eq: ''}}}, id: { $not: {$eq: "
Creating the following query:
{username: 'Yossarian', {password: $not: {{$eq: ''}}}, id: { $not: {$eq: "', password: '' }
We are now well on our way. This query is searching for a Document where the username is Yossarian, and that the password is not empty, and finally that the id is not equal to ', password: '
However, there is an error, the $eq operator is not completed. We will now use the password parameter/field to complete this injection attack. As a reminder, the original structure of the query within the application is:
{ username: '<username>', password: '<password>' }
We have completed the injection into the username, now we must investigate the password parameter. If we simply inject: " }} this will complete the $eq operator, thus our password becomes:
" }}
This ultimately - combining the above username injection and the new password injection - the C#.NET code creates an expression in the code that looks like:
{username: 'Yossarian', {password: $not: {{$eq: ''}}}, id: { $not: {$eq: "', password: '" }}' }
This leaves us, finally, with one single issue to deal with! A final trailing single quote.
In some systems, it may be as simple as expanding the query to repeat the requested username, e.g., by adding , username: 'Yossarian to the password injection. This would result in a final query that looks like:
{username: 'Yossarian', {password: $not: {{$eq: ''}}}, id: { $not: {$eq: "', password: '" }}, username: 'Yossarian' }
This query works within the MongoDB Compass application, as shown in this screenshot:
Demonstration of double specification of a fieldname in MongoDB Compass
However, within the MongoDB.Drivers for C#.NET, this throws an exception, specifically related to the fact that the username field has been specified twice. So, how can we deal with this? We are left with a trailing single quote, and as we cannot specify a fieldname twice and we have already utilised the three possible fieldnames in this example, does this leave us stuck?
Well, an obvious answer would be that in a real-world scenario, it is highly unlikely that a Document would only contain these three fields, however, this is an unsatisfactory end to this injection attack!
Further, had this been a traditional SQL Injection, we could simply comment out the trailing single quote and inject whatever else we needed. However, it is not such a scenario!
As it turns out, there is a way around this issue. MongoDB supports a wide range of features, one of which is the $where operator. This will be explored in more detail later in this article. However, for now, we can use this to inject a true statement i.e., something that is always true irrespective of the specific document we are looking at, such as: 1 = 1.
The $where operator [5] can evaluate JavaScript code, and as such we can represent such a statement with the following JavaScript code:
1 == 1
Thus, we could then inject an expression such as the following:
$where: '1 == 1'
This can be used to complete our injection attack! Our completed injection attack will look like the following:
Username injection (as a reminder):
Yossarian', {password: $not: {{$eq: ''}}}, id: { $not: {$eq: "
And our password injection:
" }}, $where : '1 == 1
Resulting, in a final generated query:
{ username: 'Yossarian', {password: $not: {{$eq: ''}}}, id: { $not: {$eq: "', password: '" }}, $where : '1 == 1' }
Voila! We have the final injection working. Obviously, Yossarian, can be changed for any account that access to is desired, e.g., Admin, Administrator, Owner, Root etc.
The following screenshot shows the successful injection attack, having just been executed in the debugger:
(Note: the code was slightly modified from the earlier version, to enable the easier viewing of the attack and result!)
Here you can see the successfully loaded Document from the MongoDB, along with the injected parameters, and a final Boolean value indicating that authentication was successful, without providing the password.
Proxies
In many scenarios, it is likely that JSON objects are being sent from client to server, this may present an easier attacks vector, i.e., enabling simpler query structures, whilst requiring the attacker to establish a HTTP / Web proxy, such as BurpProxy [14] on their computer to intercept requests and modify them.
E.g., where a web application constructs a JSON structure on the client side that is sent to the server for use with a MongoDB, such as the following:
{ username: 'Yossarian', password: 'IDontKnow' }
An attacker could use a proxy to capture such a request and modify it as follows:
{ username: 'Yossarian', password: {$ne: ''} }
This is a significantly simpler attack, except for having to setup the proxy - though, this is a simple activity for any penetration tester worth their salt!
Note, that the solution - described below in the Solutions section - of using the MongoDB.Driver classes to build BSON Documents programmatically may not defend against this type of attack, especially if the code is designed to accept objects and not strings.
$Where
The $where operator is a particularly powerful feature of MongoDB, from a hacking or security perspective, this is analogous to the infamous xp_cmdshell in the, now, very old Microsoft SQL Server. Essentially, $where executes JavaScript and can accept multi-line functions.
Thus, if an attacker can either inject a $where operator or is perhaps already injecting into a $where operator, this will enable remote code execution on the MongoDB database itself.
There will be a follow-up article that explores powerful attacks that can be carried out using the $where operator and JavaScript.
Solutions
One of the most useful controls to implement to prevent injection attacks is input validation.
Input Validation will not only resolve No-SQL Injection issues, but also SQL Injection [1], Command Injection, XXE Injection, many forms of XSS and so on.
When performing input validation, it is important to white-list input, as opposed to blocking known 'bad' characters. For example, if usernames are expected to only contain lower case a-z, upper case A-Z, and 0-9 - only allow these characters. If the input data contains anything other than what is permitted, reject the request.
Other techniques include escaping input, e.g., so that when a quote is injected, it is treated as text and not a string delimiter, and thus any subsequent commands provides are also treated as text.
There are also some features as part of the MongoDB.Driver class libraries that can help, which help build up the query as a BSON Document in stages. This, however, is still not a complete solution especially where JSON documents are accepted on the server-side as objects. In fact, building up BSON documents via these APIs/Classes is the intended approach, however, as supplying textual JSON documents works and it is very similar to the multi-decade long traditional method of supplying strings for SQL Databases, many programmers and software engineers will do so.
Final Thoughts
MongoDB, along with other No-SQL databases, provides us with new opportunities for faster development, more flexible data structures - these are excellent advantages. Where ACID properties are not required, these types of databases may be the ideal solution.
However, we need to be cognizant of the fact that they are relatively new (at least in terms of widespread adoption, as NoSQL dates back decades), security risks may not have been fully analysed - and making bold claims that systems are not susceptible to injection is, just that, very bold!
References
[1] - OWASP Foundation, https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html, accessed on 23rd September 2023
[2] - MongoDB, FAQ: MongoDB Fundamentals, https://www.mongodb.com/docs/manual/faq/fundamentals/#bson, accessed on 23rd September 2023.
[3] - MongoDB, $not, https://www.mongodb.com/docs/manual/reference/operator/query/not/, accessed on 24th September 2023
[4] - MongoDB, $eq, https://www.mongodb.com/docs/manual/reference/operator/query/eq/, accessed on 24th September 2023
[5] - MongoDB, $where, https://www.mongodb.com/docs/manual/reference/operator/query/where/, accessed on 24th September 2023
[6] - MongoDB, $or, https://www.mongodb.com/docs/manual/reference/operator/query/or/, accessed on 24th September 2023
[7] - MongoDB, $and, https://www.mongodb.com/docs/manual/reference/operator/query/and/, accessed on 24th September 2023
[8] - MongoDB, $ne, https://www.mongodb.com/docs/manual/reference/operator/query/ne/, accessed on 24th September 2023
[9] - MongoDB, $lt,
https://www.mongodb.com/docs/manual/reference/operator/query/lt/, accessed on 24th September 2023
[10] - MongoDB, $lte, https://www.mongodb.com/docs/manual/reference/operator/query/lte/, accessed on 24th September 2023
[11] - MongoDB, $gt, https://www.mongodb.com/docs/manual/reference/operator/query/gt/, accessed on 24th September 2023
[12] - MongoDB, $gte, https://www.mongodb.com/docs/manual/reference/operator/query/gte/, accessed on 24th September 2023
[13] - MongoDB, $lookup, https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/, accessed on 24th September 2023
[14] - PortSwigger, Burp Proxy, https://portswigger.net/burp/documentation/desktop/tools/proxy, accessed on 24th September 2023
Cookie Notice
We use cookies to ensure that we give you the best experience on our website. Please confirm you are happy to continue.