Security advisory GHSA-42gv-77f4-r3j9: Shopware SQL Injection
Package: shopware/shopware (composer), vulnerable version: v5.4.2, fixed in: 5.4.3
CWE: CWE-89
Files changed by the fix:
engine/Shopware/Bundle/AttributeBundle/Service/SchemaOperator.php

Fix diff:
diff --git a/UPGRADE-5.4.md b/UPGRADE-5.4.md
index a6f317f033a..952fe0a8507 100644
--- a/UPGRADE-5.4.md
+++ b/UPGRADE-5.4.md
@@ -16,15 +16,16 @@ This changelog references changes done in Shopware 5.4 patch versions.
 
 ### Changes
 
-* Changed behaviour of media/temp files, which now will be deleted if they are uploaded
+* Changed input validation to fix possible authenticated SQLi vulnerability in the backend
 * Changed .htaccess file to prohibit download of .env files
+* Changed behaviour of media/temp files, which now will be deleted if they are uploaded
 * Changed `Media` resource to fix a problem with file names set via API
 * Changed "Send E-Mails" checkbox in batch processing window of order to be enabled by default again
 * Changed behaviour of inactive forms to act like any other missing page
 * Changed the notification box behaviour when products are out of stock
 * Changed API behaviour on update, when the lastStock parameter is set for a product its applied to its mainDetail aswell (like on creation)
 * Changed newsletter recipient count to work correctly with customer streams
-* Changed position of several privacy options to the privacy basic setting category
+* Changed position of several privacy options to a new basic setting category "Privacy"
 * Changed search indexer to make the keyword batch size configurable using the key `search.indexer.batchsize` in the `config.php` 
 
 ### Removals
diff --git a/engine/Shopware/Bundle/AttributeBundle/Service/SchemaOperator.php b/engine/Shopware/Bundle/AttributeBundle/Service/SchemaOperator.php
index 463eb2cb277..b78f0e67f06 100644
--- a/engine/Shopware/Bundle/AttributeBundle/Service/SchemaOperator.php
+++ b/engine/Shopware/Bundle/AttributeBundle/Service/SchemaOperator.php
@@ -49,8 +49,6 @@ class SchemaOperator
     private $nameBlacklist;
 
     /**
-     * SchemaOperator constructor.
-     *
      * @param Connection   $connection
      * @param TableMapping $tableMapping
      */
@@ -73,12 +71,19 @@ public function __construct(Connection $connection, TableMapping $tableMapping)
     public function createColumn($table, $column, $type, $defaultValue = null)
     {
         $this->validate($table, $column);
+        $defaultValue = $this->filterDefaultValue($defaultValue);
 
         if (!$type) {
             throw new \Exception('No column type provided');
         }
 
-        $sql = sprintf('ALTER TABLE `%s` ADD `%s` %s NULL DEFAULT %s', $table, $column, $type, $defaultValue);
+        $sql = sprintf('ALTER TABLE `%s` ADD `%s` %s NULL DEFAULT %s',
+            $table,
+            $column,
+            $type,
+            $defaultValue
+        );
+
         $this->connection->executeQuery($sql);
     }
 
@@ -103,7 +108,16 @@ public function changeColumn($table, $originalName, $newName, $type, $defaultVal
             throw new \Exception('No column type provided');
         }
 
-        $sql = sprintf('ALTER TABLE `%s` CHANGE `%s` `%s` %s NULL DEFAULT %s;', $table, $originalName, $newName, $type, $defaultValue);
+        $this->validateField($newName);
+        $defaultValue = $this->filterDefaultValue($defaultValue);
+
+        $sql = sprintf('ALTER TABLE `%s` CHANGE `%s` `%s` %s NULL DEFAULT %s;',
+            $table,
+            $originalName,
+            $newName,
+            $type,
+            $defaultValue
+        );
 
         $this->connection->executeQuery($sql);
     }
@@ -163,15 +177,55 @@ private function validate($table, $name)
             throw new \Exception('No column name provided');
         }
 
+        $this->validateField($table);
+        $this->validateField($name);
+
         if (!$this->tableMapping->isAttributeTable($table)) {
             throw new \Exception(sprintf('Provided table is no attribute table: %s', $table));
         }
         if ($this->tableMapping->isIdentifierColumn($table, $name)) {
             throw new \Exception(sprintf('Provided column is an identifier column: %s', $name));
         }
+
         $lowerCaseName = strtolower($name);
         if (in_array($lowerCaseName, $this->nameBlacklist)) {
             throw new \Exception(sprintf('Provided name %s is a reserved keyword.', $name));
         }
     }
+
+    /**
+     * @param string $field
+     *
+     * @throws \Exception
+     */
+    private function validateField($field)
+    {
+        if (strlen($field) > 64) {
+            throw new \Exception('Maximum length: 64 chars');
+        }
+
+        if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field)) {
+            throw new \Exception(sprintf('Invalid chars in %s', $field));
+        }
+    }
+
+    /**
+     * @param null|float|int|string $defaultValue
+     *
+     * @return null|float|int|string
+     */
+    private function filterDefaultValue($defaultValue)
+    {
+        if (!is_string($defaultValue)) {
+            return $defaultValue;
+        }
+
+        if ($defaultValue === 'NULL') {
+            return $defaultValue;
+        }
+
+        return $this->connection->quote(
+            str_replace(['`', '\''], '', $defaultValue)
+        );
+    }
 }