CVE-2023-41892 (published 2023-09-13T20:15Z)

Craft CMS is a platform for creating digital experiences. This is a high-impact, low-complexity attack vector. Users running Craft installations before 4.4.15 are encouraged to update to at least that version to mitigate the issue. This issue has been fixed in Craft CMS 4.4.15.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/craftcms/cms
commit_details: repo_url: https://github.com/craftcms/cms
author: brandonkelly
author_date: 2023-06-27 16:01:14-07:00
msg: Call beforeAction() up front
repository: repo_url: https://github.com/craftcms/cms
repo_name: craftcms/cms
description: Build bespoke content experiences with Craft.
repo_language: PHP
owner: craftcms
file_changes: filename: AssetIndexesController.php
change_type: ModificationType.MODIFY
diff: @@ -36,11 +36,15 @@ class AssetIndexesController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // No permission no bueno
         $this->requirePermission('utility:asset-indexes');
         $this->requireAcceptsJson();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: AssetSettingsController.php
change_type: ModificationType.MODIFY
diff: @@ -25,10 +25,14 @@ class AssetSettingsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All user settings actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: BaseElementsController.php
change_type: ModificationType.MODIFY
diff: @@ -27,10 +27,14 @@ abstract class BaseElementsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All actions require control panel requests
         $this->requireCpRequest();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: ConditionsController.php
change_type: ModificationType.MODIFY
diff: @@ -34,6 +34,12 @@ class ConditionsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
+        $this->requireCpRequest();
+
         $baseConfig = Json::decodeIfJson($this->request->getBodyParam('config'));
         $config = $this->request->getBodyParam($baseConfig['name']);
         $newRuleType = ArrayHelper::remove($config, 'new-rule-type');
@@ -48,7 +54,7 @@ class ConditionsController extends Controller
             $this->_condition->addConditionRule($rule);
         }
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: FieldsController.php
change_type: ModificationType.MODIFY
diff: @@ -38,10 +38,14 @@ class FieldsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All field actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     // Groups

programming_language: PHP
filename: FsController.php
change_type: ModificationType.MODIFY
diff: @@ -33,10 +33,14 @@ class FsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All asset volume actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: GraphqlController.php
change_type: ModificationType.MODIFY
diff: @@ -59,13 +59,17 @@ class GraphqlController extends Controller
             throw new NotFoundHttpException(Craft::t('yii', 'Page not found.'));
         }
 
-        Craft::$app->requireEdition(Craft::Pro);
-
         if ($action->id === 'api') {
             $this->enableCsrfValidation = false;
         }
 
-        return parent::beforeAction($action);
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
+        Craft::$app->requireEdition(Craft::Pro);
+
+        return true;
     }
 
     /**

programming_language: PHP
filename: ImageTransformsController.php
change_type: ModificationType.MODIFY
diff: @@ -31,10 +31,14 @@ class ImageTransformsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All image transform actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: LivePreviewController.php
change_type: ModificationType.MODIFY
diff: @@ -36,12 +36,16 @@ class LivePreviewController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // Mark this as a Live Preview request
         if ($action->id === 'preview') {
             $this->request->setIsLivePreview(true);
         }
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: PluginStoreController.php
change_type: ModificationType.MODIFY
diff: @@ -35,10 +35,14 @@ class PluginStoreController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All plugin store actions require an admin
         $this->requireAdmin(false);
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: PluginsController.php
change_type: ModificationType.MODIFY
diff: @@ -28,10 +28,14 @@ class PluginsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All plugin actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: RebrandController.php
change_type: ModificationType.MODIFY
diff: @@ -35,8 +35,13 @@ class RebrandController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         Craft::$app->requireEdition(Craft::Pro);
-        return parent::beforeAction($action);
+
+        return true;
     }
 
     /**

programming_language: PHP
filename: RoutesController.php
change_type: ModificationType.MODIFY
diff: @@ -26,10 +26,14 @@ class RoutesController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All route actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: SectionsController.php
change_type: ModificationType.MODIFY
diff: @@ -36,10 +36,14 @@ class SectionsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All section actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: SitesController.php
change_type: ModificationType.MODIFY
diff: @@ -36,10 +36,14 @@ class SitesController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All actions require an admin account
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: StructuresController.php
change_type: ModificationType.MODIFY
diff: @@ -41,6 +41,10 @@ class StructuresController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         $this->requirePostRequest();
         $this->requireAcceptsJson();
 
@@ -80,7 +84,7 @@ class StructuresController extends Controller
             throw new NotFoundHttpException('Element not found');
         }
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: SystemMessagesController.php
change_type: ModificationType.MODIFY
diff: @@ -27,12 +27,16 @@ class SystemMessagesController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         Craft::$app->requireEdition(Craft::Pro);
 
         // Make sure they have access to the System Messages utility
         $this->requirePermission('utility:system-messages');
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: SystemSettingsController.php
change_type: ModificationType.MODIFY
diff: @@ -40,10 +40,14 @@ class SystemSettingsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All system setting actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: UserSettingsController.php
change_type: ModificationType.MODIFY
diff: @@ -28,6 +28,10 @@ class UserSettingsController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All user settings actions require an admin
         $this->requireAdmin();
 
@@ -35,7 +39,7 @@ class UserSettingsController extends Controller
             Craft::$app->requireEdition(Craft::Pro);
         }
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
filename: VolumesController.php
change_type: ModificationType.MODIFY
diff: @@ -35,10 +35,14 @@ class VolumesController extends Controller
      */
     public function beforeAction($action): bool
     {
+        if (!parent::beforeAction($action)) {
+            return false;
+        }
+
         // All asset volume actions require an admin
         $this->requireAdmin();
 
-        return parent::beforeAction($action);
+        return true;
     }
 
     /**

programming_language: PHP
repo_url: https://github.com/craftcms/cms
commit_details: repo_url: https://github.com/craftcms/cms
author: brandonkelly
author_date: 2023-06-27 17:16:27-07:00
msg: Take two
repository: repo_url: https://github.com/craftcms/cms
repo_name: craftcms/cms
description: Build bespoke content experiences with Craft.
repo_language: PHP
owner: craftcms
file_changes: filename: ConditionsController.php
change_type: ModificationType.MODIFY
diff: @@ -41,7 +41,7 @@ class ConditionsController extends Controller
 
         $this->requireCpRequest();
 
-        $baseConfig = Json::decodeIfJson($this->request->getBodyParam('config'));
+        $baseConfig = Component::cleanseConfig(Json::decodeIfJson($this->request->getBodyParam('config')));
         $config = Component::cleanseConfig($this->request->getBodyParam($baseConfig['name']));
         $newRuleType = ArrayHelper::remove($config, 'new-rule-type');
         $conditionsService = Craft::$app->getConditions();

programming_language: PHP
repo_url: https://github.com/craftcms/cms
commit_details: repo_url: https://github.com/craftcms/cms
author: brandonkelly
author_date: 2023-06-27 16:35:52-07:00
msg: Fixed an RCE vulnerability
repository: repo_url: https://github.com/craftcms/cms
repo_name: craftcms/cms
description: Build bespoke content experiences with Craft.
repo_language: PHP
owner: craftcms
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -6,8 +6,10 @@
 - The `up`, `migrate/up`, and `migrate/all` commands now revert any project config changes created by migrations on failure.
 - The `up`, `migrate/up`, and `migrate/all` commands now prompt to restore the backup created at the outset of the command, or recommend restoring a backup, on failure.
 - Added `craft\console\controllers\BackupTrait::restore()`.
+- Added `craft\helpers\Component::cleanseConfig()`.
 - Fixed a bug where Single entries weren’t getting preloaded for template macros, if the template body wasn‘t rendered. ([#13312](https://github.com/craftcms/cms/issues/13312))
 - Fixed a bug where asset folders could get dynamically created for elements with temporary slugs. ([#13311](https://github.com/craftcms/cms/issues/13311))
+- Fixed an RCE vulnerability.
 
 ## 4.4.14 - 2023-06-13
 

programming_language: Markdown
filename: ConditionsController.php
change_type: ModificationType.MODIFY
diff: @@ -11,6 +11,7 @@ use Craft;
 use craft\base\conditions\ConditionInterface;
 use craft\base\conditions\ConditionRuleInterface;
 use craft\helpers\ArrayHelper;
+use craft\helpers\Component;
 use craft\helpers\Json;
 use craft\web\Controller;
 use Illuminate\Support\Collection;
@@ -41,7 +42,7 @@ class ConditionsController extends Controller
         $this->requireCpRequest();
 
         $baseConfig = Json::decodeIfJson($this->request->getBodyParam('config'));
-        $config = $this->request->getBodyParam($baseConfig['name']);
+        $config = Component::cleanseConfig($this->request->getBodyParam($baseConfig['name']));
         $newRuleType = ArrayHelper::remove($config, 'new-rule-type');
         $conditionsService = Craft::$app->getConditions();
         $this->_condition = $conditionsService->createCondition($config);

programming_language: PHP
filename: Component.php
change_type: ModificationType.MODIFY
diff: @@ -81,6 +81,27 @@ class Component
         return true;
     }
 
+    /**
+     * Cleanses a component config of any `on X` or `as X` keys.
+     *
+     * @param array $config
+     * @return array
+     * @since 4.4.15
+     */
+    public static function cleanseConfig(array $config): array
+    {
+        foreach ($config as $key => $value) {
+            if (is_string($key) && (str_starts_with($key, 'on ') || str_starts_with($key, 'as '))) {
+                unset($config[$key]);
+                continue;
+            }
+            if (is_array($value)) {
+                $config[$key] = static::cleanseConfig($value);
+            }
+        }
+        return $config;
+    }
+
     /**
      * Instantiates and populates a component, and ensures that it is an instance of a given interface.
      *

programming_language: PHP
filename: ComponentHelperTest.php
change_type: ModificationType.MODIFY
diff: @@ -115,6 +115,16 @@ class ComponentHelperTest extends TestCase
         self::assertStringContainsString($needle, Component::iconSvg($icon, $label));
     }
 
+    /**
+     * @dataProvider cleanseConfigDataProvider
+     * @param array $expected
+     * @param array $config
+     */
+    public function testCleanseConfig(array $expected, array $config)
+    {
+        self::assertSame($expected, Component::cleanseConfig($config));
+    }
+
     /**
      * @return array
      */
@@ -304,4 +314,21 @@ class ComponentHelperTest extends TestCase
             'aria-hidden' => ['aria-hidden="true"', '<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>', 'Default'],
         ];
     }
+
+    /**
+     * @return array
+     */
+    public function cleanseConfigDataProvider(): array
+    {
+        return [
+            [
+                ['f' => 'foo', 'b' => 'bar'],
+                ['f' => 'foo', 'b' => 'bar', 'as f' => 'f', 'on b' => 'b'],
+            ],
+            [
+                ['nested' => ['f' => 'foo', 'b' => 'bar']],
+                ['nested' => ['f' => 'foo', 'b' => 'bar', 'as f' => 'f', 'on b' => 'b']],
+            ],
+        ];
+    }
 }

programming_language: PHP