We have scanned Pimcore 6.2.0 and identified multiple critical vulnerabilities including a command injection vulnerability and SQL injection vulnerability which both can be exploited into a full remote code execution. Both vulnerabilities were fixed in Pimcore 6.2.1.

The truncated analysis results are available in our RIPS demo application. Please note that we limited the results to the issues described in this post in order to ensure a fix is available.

From Exif Data To Code Execution

Exiftool is a linux program which allows manipulation of image meta data called exif data. Whenever an image is processed by PimCore a shell command is executed to run the exiftool script with the image filename as a parameter. For this purpose a JSON object is passed through a GET-variable which can be controlled by an attacker to inject straight into a shell command.

vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/AssetController.php

1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
public function downloadImageThumbnailAction(Request $request)
{
    /*...*/
    $config = $this->decodeJson($request->get('config'));
    /*...*/
    $thumbnailConfig->setFormat($config['format']);
    /*...*/
    $exiftool = \Pimcore\Tool\Console::getExecutable('exiftool');
    if ($thumbnailConfig->getFormat() == 'JPEG' && $exiftool 
    && isset($config['dpi']) && $config['dpi']) {
        \Pimcore\Tool\Console::exec($exiftool . 
        ' -overwrite_original -xresolution=' . $config['dpi'] . 
        ' -yresolution=' . $config['dpi'] . ' -resolutionunit=inches ' . 
        escapeshellarg($thumbnailFile));
}

In line 1063 of the downloadImageThumbnailAction() method the $config parameter, storing a JSON object, is received from the HTTP request, decoded and stored into the $config variable. After validating that the image is a JPEG and the exiftool script is installed on line 1068, the $config['dpi'] variable is embedded unsanitized into the OS command and executed on line 1070. Although the $thumbnailFile variable is correctly sanitized by passing it through escapeshellarg(), the developers of PimCore have not applied this function to the dpi key of the $config parameter.

Preparing an Array: SQL’s IN Keyword

Although Pimcore is making use of prepared statements all over the application, we could confirm multiple SQL injections detected by our RIPS scanner requiring the role of a back end user having access to the objects section. One of those flaws comes from the abstract database layer which fails to provide a direct method to safely embed an array into a SQL query. Often developers feel the urge to let a user provide a list or an array of ids which shall be deleted, updated, or selected. To solve this problem they make use of SQL’s IN keyword. We will now see an example on how this can go wrong:

vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php

921
922
923
924
925
926
927
928
929
930
931
932
933
public function addCollectionsAction(Request $request)
{
    $this->checkPermission('objects');
    $ids = $this->decodeJson($request->get('collectionIds'));
    /*...*/
    if ($ids) {
        $db = \Pimcore\Db::get();
        $query = 'select * from classificationstore_groups g, 
            classificationstore_collectionrelations c where colId IN (' 
            . implode(',', $ids)
            . ') and g.id = c.groupId';
        /*...*/
        $groupsData = $db->fetchAll($query);

The addCollectionsAction() method can be called through routing directly. User input is received through the collectionIds parameter on line 924. The array of ids is then sent to implode(), transforming the array into a comma separated string of its values. The builtin function implode() does neither sanitize nor validate and therefore the resulting string should not be embedded directly into the SQL query, which is done on line 930 leading to a SQL injection.

In contrast, modern database abstraction layers build a wrapper around PDO’s prepare() method. They will prepare a SQL query containing as many ? placeholders as there are values in the array, and pass the potentially malicious array of the user as an argument to the execute() method of PDOStatement instance.

Forging a Drive By Exploit

A missing CSRF token enables an attacker to exploit the vulnerabilities via CSRF which results in a drive by exploit where an administrator is lured onto a malicious page embedding a form which is auto submitted to send a request to the web page, including the cookies of the administrator, allowing the exploitation of the remote code execution. To exploit the SQL injection, a more sophisticated attack (as seen here) allows extraction of data via side channels.

Summary

The intrinsic vulnerabilities described in this blog post sketch the imbalance between attackers on the one hand - requiring only a single point of failure - and the defenders on the other hand who miss a single flaw in the system leading to a full compromise. The threat of CSRF vulnerabilities is often underestimated, however, in targeted attacks launched by determined adversaries they pose an attractive step stone for network intrusion and social engineering. In this specific case we would like to thank the Pimcore developers and gladly appreciate their security awareness due to their instant response and fix for the the issues.

Timeline

DateEvent
09/09/2019First contact with and vulnerability report to vendor
09/09/2019 + a few hoursAcknowledgment, confirmation and commit of correct patch
09/18/2019Release of PimCore 6.2.1