
Our team searched for bugs in the source code of Cockpit, an open-source content management system. Here is the description of Cockpit from its official site:
Cockpit is a headless CMS with an API-first approach that puts content first. It is designed to simplify the process of publication by separating content management from content consumption on the client side.
Cockpit is focusing just on the back-end work to manage content. Rather than worry about delivery of content through pages, its goal is to provide structured content across different channels via a simple API.
While investigating the Cockpit source code, we discovered numerous vulnerabilities. Attackers could exploit them to take control of any user account and perform remote code execution.
In this article, I will talk about the technical details and demonstrate how these vulnerabilities can be exploited.
Extracting user account names
In the source code, we found two methods vulnerable to NoSQL injection, which can be used to extract application usernames. Neither of these methods requires authentication.
NoSQL injection in /auth/check
(CVE-2020-35846)
Let’s consider the check
method of the Auth controller responsible for authenticating app users:

and the authenticate
function of the cockpit module:

As you can see, the code does not check the type of the user parameter, which allows embedding an object with arbitrary MongoDB operators in the query.
This is blind injection, so for successful exploitation you need to find a way to return the result of the condition.
Having analyzed the method source code, we developed a technique. In essence, we pass an array (instead of a string) in the password parameter. This results in a warning, displayed by the password_verify function, about an invalid value type:

Now I will demonstrate a few more ways to exploit blind NoSQL injection:
1. Using the $eq
operator
The
$eq
operator matches documents where the value of a field equals the specified value.
For example, you can use it to bruteforce names with a dictionary.


2. Using the $regex
operator
Provides regular expression capabilities for pattern matching strings in queries
You can use it to bruteforce the names of all application users.


We can speed up bruteforcing by adding the $nin
operator to the query, which will exclude any users that have already been found:
$nin
selects the documents where the field value is not in the specified array


We can tweak this by adding a fixed quantifier to the regular expression for finding or limiting the length of the string:



3. Using the $func operator of the MongoLite library (used by default)
This non-standard operator allows calling the criterion function $b
(any PHP function with a single parameter), which takes a single argument equal to field $a
(in this case, the user field):

By passing the PHP function var_dump
or var_export
as the argument, we will turn blind injection into classic in-band injection. With a single query, we can get the names of all app users:

NoSQL injection in /auth/requestreset
requestreset
method of the Auth controller responsible for creating the password reset token:

As in the previous case, there is no type check for the user parameter. Exploitation is similar, but without any difficulties such as password or CSRF token verification:

Extracting password reset tokens
Cockpit, like many other web applications, allows resetting account passwords.
We discovered two methods that are vulnerable to NoSQL injection and allow obtaining the password reset token for any user.
NoSQL injection in /auth/resetpassword
(CVE-2020-35847)
resetpassword
method of the Auth controller, which is responsible for changing the user password using the reset token:

There is no type checking for the token parameter, so you can extract existing tokens with the following query:

NoSQL injection in /auth/newpassword
(CVE-2020-35848)
newpassword
method of the Auth controller, which is responsible for displaying the user password reset form:

And, again, there is no type checking for the token parameter. The query is similar to the previous one:

User account compromise
Now, being able to get password reset tokens, we can compromise any user account we are interested in. This takes just a few steps:
1. Access /auth/requestreset
to generate a token for resetting the password of the selected user:

2. Extract tokens by using one of the methods just described (/auth/resetpassword
or /auth/newpassword
):

3. Extract user account data (username, password hash, API key, password reset token) using the /auth/newpassword
method and the password reset tokens obtained in the previous step:


With this data in hand, we can then:
- Use the application with the API key.
- Bruteforce the account password from the hash.
- Change the account password by using the
/auth/resetpassword
method:

Remote Code Execution
Easy RCE
Having compromised the administrator account, we can upload a web shell using Cockpit’s standard Finder component in order to achieve remote code execution:


PHP injection in the UtilArrayQuery::buildCondition
method of the MongoLite library
Let’s consider the method registerCriteriaFunction
of the Database
class, which creates a condition function for the specified criteria (filters) of the document:

and the associated function buildCondition
of the UtilArrayQuery
class:

Make note of the $key
variable, which contains the field name. Its content is plugged into the future string literal as-is, without being escaped.
So by controlling the content of the $key
variable, we can escape from the string literal (break it) with a single quote in order to inject arbitrary PHP code.
To demonstrate the vulnerability, we will use the /accounts/find
method (authentication required). This method supports custom criteria (filters), which means it will allow us to place arbitrary content in $key
:

Conclusion
In this article, I have demonstrated several ways to exploit blind NoSQL injection, a way for an unauthenticated user to take over any account, and remote code execution in the MongoLite library.
Everyone should update to the latest version (>= 0.12.0) right away.
The disclosure timeline:
- October 14, 2020 – Vulnerability information (#1) sent to developer
- October 15, 2020 – Bugfixes released (commit
79fc963
, commit33e7199
) - October 26, 2020 – Patch released (commit
2a385af
) - March 15, 2021 – Vulnerability information (#2) sent to developer
- March 17, 2021 – Bugfix released (commit
b40d6bd
)