The Pimcore Platform provides software for central management of corporate data. With over 100,000 clients across 56 countries, including some major vendors, it has become a trusted choice for businesses worldwide. Available in both an Enterprise subscription as well as an Open Source Community Edition with a growing community of developers and users.
We make a consistent effort to enhance the technology powering our Clean Code solution by frequently scanning open-source projects and assessing the outcomes. In the case of Pimcore, our engine reported an interesting limited directory traversal vulnerability. After analyzing the finding we found an additional SQL Injection vulnerability in the same endpoint. Leveraging those two vulnerabilities, an admin that clicks on an attacker’s crafted link will execute arbitrary code on the server.
Pimcore Vulnerabilities Impact
Pimcore versions prior to 10.5.19 are susceptible to both a path traversal and an SQL injection vulnerability in the create-csv
endpoint tracked as CVE-2023-28438. The two vulnerabilities can be exploited with a single GET request. Because of this, an attacker can create a malicious link, which can cause the execution of arbitrary code when accessed by an admin.
Technical Details
In this section, we will discuss the technical details of the vulnerabilities and explain how an attacker could combine them to create a one-click exploit that will deploy a web shell on the server.
Limited Arbitrary File Write and Path Traversal
Scanning Pimcore with SonarCloud uncovered an interesting path traversal issue caused by passing user-controlled data as the filename parameter of fopen
. You can inspect the finding directly on SonarCloud:
Try it by yourself on SonarCloud!
The underlined feature is in the admin panel of Pimcore which enables the display of statistical reports on various aspects of the website. An admin can create custom reports, view them directly from the panel, or download the data in CSV format:
Upon further inspection of the vulnerable function createCsvAction
, we found out that the user-controlled data is passed through the admin/reports/custom-report/create-csv
endpoint’s exportFile
parameter. Although this endpoint is only accessible by admins, it is a GET request endpoint with no CSRF protection, thus manipulating an admin to click on a link is enough.
The value of the exportFile
parameter is appended to the web root path without prior sanitization, allowing an attacker to control the extension as well as traverse back in the folder path.
On continued inspection of the code, we can see that the user-controlled path will end up opening a file in “append” mode. Writing the getData
function’s output to it using fputcsv
:
File in Github
Up until now, an attacker can control the CSV output file path, name, and extension. Although this allows the creation of PHP files on the server, an attacker will need to control the file content as well in order to execute arbitrary code. Here enters the second vulnerability, an SQL Injection in the getData
function.
1st SQL Injection sink
Looking at the createCsvAction
function from earlier, the inputs an attacker can control are $drillDownFilters
and $filters
, which are passed on to getBaseQuery
:
File in Github
Two SQL queries are issued with the result of the getBaseQuery
function:
$baseQuery[‘count’]
: a query that returns the number of results usingCOUNT(*)
will be used in$db->fetchOne.
$baseQuery[‘data’]
: will end up in$db->fetchAllAssociative
and fetch the results.
This is how the getBaseQuery
function that prepares those two queries looks like:
File in Github
At first glance, we noticed an injection at the $data
parameter, the SQL query's SELECT
fields are not sanitized. The implode('`,`', $fields)
can simply be escaped with backticks.
In order to control the $fields
parameter we need to set the $filters['operator']
attribute accordingly (in the code snippet only '=' is shown but there are other options) and then the 'property'
attribute will be appended to it. Immediately after a $condition
string will be created. So in order to control the $fields
value the $condition
string will be present.
However, while it seems like there is a simple SQL injection at $data
, the $condition
variable is concatenated to the end of both queries (count
and data
). And due to the quotation escaping (done using the functions $db->quoteIdentifier
and $db->quote
), any field containing a backtick character (`) will be doubled and thus making the query's syntax invalid.
We can of course comment out the rest of the query (using --
or ;
) to avoid the syntax breaking $condition
. But the $total
query also has the broken $condition
, and later be used in the line $db->fetchOne($baseQuery['count'])
before fetching with the SQL Injected data
query, thus raising an exception and not executing the SQL Injection.
2nd SQL Injection sink
So we have an SQL Injection, but exploiting it will always cause a syntax error. Is there any other way to somehow ignore the $condition
string?
Some of you probably already noticed that before every $condition
there is the $sql
parameter, which is returned from $this->getBaseQuery(...)
. If there is an SQL Injection in that function as well we can end the query before the syntax error.
Auditing the buildQueryString
function we found another SQL Injection sink but now using the $drillDownFilters
parameter. Though the value is being quoted, the field isn't. An attacker can use this sync to comment out the broken $condition
and execute arbitrary SQL queries.
Exploitation - connecting everything together
So an attacker can control the output file and inject SQL to the function that fetches results which will end up in that file. Having the export file path pointing to a PHP file in the web root is straightforward using:
A PHP file will execute also if there is the PHP declaration randomly in the file, meaning a file doesn't have to start with <?php
, so we don't have to worry about that.
But how can an attacker exploit the SQL Injection to result in arbitrary content?
Having multiple queries, one that inserts custom data and another that fetches it is possible but makes the exploit more complicated. Going back to our SQL query, the injection is in the SELECT fields, so we can use the CASE expression.
Lastly, there are two parameters needed for the get request:
headers=true
is to output the field names to the CSVname=Quality_Attributes
is a default name of a report from the demo app (in order to execute the vulnerable function the name has to be a valid report)
Combining those 2 vulnerabilities from 3 sinks in 1 GET request an attacker could create a malicious link that will deploy a web shell on the server.
Patch
Both vulnerabilities were fixed in Pimcore version 10.5.19:
- The SQL Injection was fixed by adding
db->quoteIdentifier(...)
in the field name as well.
- The path traversal was fixed by:
- Verifying that the extension is “.csv”
- Normalizing the path to prevent traversing
Timeline
Date | Action |
2023-02-20 | We reported all issues to Vendor |
2023-03-15 | Vendor released patch version 10.5.19 |
2023-03-22 | CVE-2023-28438 and security advisory released |
Summary
The focus of our blog post was on our success in identifying and utilizing two distinct vulnerabilities with a single GET request, ultimately leading to code execution. This serves as a powerful demonstration of our product's capability to detect security flaws, and we also highlighted the step-by-step process we followed from analyzing the results to creating a weaponized exploit.
We would like to thank the maintainers again for the quick response and for handling the situation professionally.