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
Let’s consider the
check method of the Auth controller responsible for authenticating app users:
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
$eqoperator 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
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:
$ninselects 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_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
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
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
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:
/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 (
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
Remote Code Execution
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
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
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
- October 26, 2020 – Patch released (commit
- March 15, 2021 – Vulnerability information (#2) sent to developer
- March 17, 2021 – Bugfix released (commit