bagisto - CSV Formula Injection in Create New Product
Description
Bagisto is an open source laravel eCommerce platform. When product data that begins with a spreadsheet formula character (for example =, +, -, or @) is accepted and later exported or saved into a CSV and opened in spreadsheet software, the spreadsheet will interpret that cell as a formula. This allows an attacker to supply a CSV field (e.g., product name) that contains a formula which may be evaluated by a victim’s spreadsheet application — potentially leading to data exfiltration and remote command execution (via older Excel exploits / OLE/cmd constructs or Excel macros). This vulnerability is fixed in 2.3.8.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
bagisto/bagistoPackagist | < 2.3.8 | 2.3.8 |
Affected products
1Patches
18076c708498afix: applied security fixes to product attributes, including short description, long description, and other TinyMCE-enabled fields
7 files changed · +262 −10
CHANGELOG.md+2 −0 modified@@ -10,6 +10,8 @@ This changelog consists of the bug & security fixes and new features being inclu * Refined TinyMCE editor integration and applied related security fixes. +* Applied security fixes to product attributes, including short description, long description, and other TinyMCE-enabled fields. + * Fixed an issue where the description was not updating correctly during channel updates. * #10971 - Fixed an issue where updating a field without changing the image caused the image to break or not display correctly.
composer.json+2 −1 modified@@ -44,7 +44,8 @@ "pusher/pusher-php-server": "^7.0", "shetabit/visitor": "^4.1", "spatie/laravel-responsecache": "^7.4", - "spatie/laravel-sitemap": "^7.3" + "spatie/laravel-sitemap": "^7.3", + "stevebauman/purify": "^6.3" }, "require-dev": { "bagisto/laravel-datafaker": "2.3.*",
composer.lock+68 −2 modified@@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc52a94d30562eb739cf3a489ceecd7c", + "content-hash": "2f992bd3a65209139a1efcb24489d967", "packages": [ { "name": "astrotomic/laravel-translatable", @@ -8005,6 +8005,72 @@ ], "time": "2025-01-13T13:04:43+00:00" }, + { + "name": "stevebauman/purify", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/stevebauman/purify.git", + "reference": "3acb5e77904f420ce8aad8fa1c7f394e82daa500" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stevebauman/purify/zipball/3acb5e77904f420ce8aad8fa1c7f394e82daa500", + "reference": "3acb5e77904f420ce8aad8fa1c7f394e82daa500", + "shasum": "" + }, + "require": { + "ezyang/htmlpurifier": "^4.17", + "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": ">=7.4" + }, + "require-dev": { + "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^8.0|^9.0|^10.0|^11.5.3" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Purify": "Stevebauman\\Purify\\Facades\\Purify" + }, + "providers": [ + "Stevebauman\\Purify\\PurifyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Stevebauman\\Purify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steve Bauman", + "email": "steven_bauman@outlook.com" + } + ], + "description": "An HTML Purifier / Sanitizer for Laravel", + "keywords": [ + "Purifier", + "clean", + "cleaner", + "html", + "laravel", + "purification", + "purify" + ], + "support": { + "issues": "https://github.com/stevebauman/purify/issues", + "source": "https://github.com/stevebauman/purify/tree/v6.3.1" + }, + "time": "2025-05-21T16:53:09+00:00" + }, { "name": "symfony/console", "version": "v7.3.3", @@ -13923,6 +13989,6 @@ "ext-pdo_mysql": "*", "ext-tokenizer": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" }
config/purify.php+115 −0 added@@ -0,0 +1,115 @@ +<?php + +use Stevebauman\Purify\Definitions\Html5Definition; + +return [ + + /* + |-------------------------------------------------------------------------- + | Default Config + |-------------------------------------------------------------------------- + | + | This option defines the default config that is provided to HTMLPurifier. + | + */ + + 'default' => 'default', + + /* + |-------------------------------------------------------------------------- + | Config sets + |-------------------------------------------------------------------------- + | + | Here you may configure various sets of configuration for differentiated use of HTMLPurifier. + | A specific set of configuration can be applied by calling the "config($name)" method on + | a Purify instance. Feel free to add/remove/customize these attributes as you wish. + | + | Documentation: http://htmlpurifier.org/live/configdoc/plain.html + | + | Core.Encoding The encoding to convert input to. + | HTML.Doctype Doctype to use during filtering. + | HTML.Allowed The allowed HTML Elements with their allowed attributes. + | HTML.ForbiddenElements The forbidden HTML elements. Elements that are listed in this + | string will be removed, however their content will remain. + | CSS.AllowedProperties The Allowed CSS properties. + | AutoFormat.AutoParagraph Newlines are converted in to paragraphs whenever possible. + | AutoFormat.RemoveEmpty Remove empty elements that contribute no semantic information to the document. + | + */ + + 'configs' => [ + + 'default' => [ + 'Core.Encoding' => 'utf-8', + 'HTML.Doctype' => 'HTML 4.01 Transitional', + 'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,b,u,strong,i,em,s,del,a[href|title],ul,ol,li,p[style],br,span,img[width|height|alt|src],blockquote', + 'HTML.ForbiddenElements' => '', + 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align', + 'AutoFormat.AutoParagraph' => false, + 'AutoFormat.RemoveEmpty' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | HTMLPurifier definitions + |-------------------------------------------------------------------------- + | + | Here you may specify a class that augments the HTML definitions used by + | HTMLPurifier. Additional HTML5 definitions are provided out of the box. + | When specifying a custom class, make sure it implements the interface: + | + | \Stevebauman\Purify\Definitions\Definition + | + | Note that these definitions are applied to every Purifier instance. + | + | Documentation: http://htmlpurifier.org/docs/enduser-customize.html + | + */ + + 'definitions' => Html5Definition::class, + + /* + |-------------------------------------------------------------------------- + | HTMLPurifier CSS definitions + |-------------------------------------------------------------------------- + | + | Here you may specify a class that augments the CSS definitions used by + | HTMLPurifier. When specifying a custom class, make sure it implements + | the interface: + | + | \Stevebauman\Purify\Definitions\CssDefinition + | + | Note that these definitions are applied to every Purifier instance. + | + | CSS should be extending $definition->info['css-attribute'] = values + | See HTMLPurifier_CSSDefinition for further explanation + | + */ + + 'css-definitions' => null, + + /* + |-------------------------------------------------------------------------- + | Serializer + |-------------------------------------------------------------------------- + | + | The storage implementation where HTMLPurifier can store its serializer files. + | If the filesystem cache is in use, the path must be writable through the + | storage disk by the web server, otherwise an exception will be thrown. + | + */ + + 'serializer' => [ + 'driver' => env('CACHE_STORE', env('CACHE_DRIVER', 'file')), + 'cache' => \Stevebauman\Purify\Cache\CacheDefinitionCache::class, + ], + + // 'serializer' => [ + // 'disk' => env('FILESYSTEM_DISK', 'local'), + // 'path' => 'purify', + // 'cache' => \Stevebauman\Purify\Cache\FilesystemDefinitionCache::class, + // ], + +];
packages/Webkul/Admin/src/Http/Controllers/Catalog/ProductController.php+1 −1 modified@@ -151,7 +151,7 @@ public function update(ProductForm $request, int $id) { Event::dispatch('catalog.product.update.before', $id); - $product = $this->productRepository->update(request()->all(), $id); + $product = $this->productRepository->update($request->all(), $id); Event::dispatch('catalog.product.update.after', $product);
packages/Webkul/Admin/src/Http/Requests/ProductForm.php+44 −6 modified@@ -5,6 +5,7 @@ use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Str; use Webkul\Admin\Validations\ProductCategoryUniqueSlug; +use Webkul\Attribute\Enums\AttributeTypeEnum; use Webkul\Core\Rules\Decimal; use Webkul\Core\Rules\Slug; use Webkul\Product\Repositories\ProductAttributeValueRepository; @@ -19,6 +20,20 @@ class ProductForm extends FormRequest */ protected $rules; + /** + * Product instance. + * + * @var \Webkul\Product\Contracts\Product + */ + protected $product; + + /** + * Product editable attributes. + * + * @var \Illuminate\Database\Eloquent\Collection + */ + protected $productEditableAttributes; + /** * Max video upload size. * @@ -55,9 +70,9 @@ public function authorize() */ public function rules() { - $product = $this->productRepository->find($this->id); + $this->product = $this->productRepository->find($this->id); - $this->rules = array_merge($product->getTypeInstance()->getTypeValidationRules(), [ + $this->rules = array_merge($this->product->getTypeInstance()->getTypeValidationRules(), [ 'sku' => ['required', 'unique:products,sku,'.$this->id, new Slug], 'url_key' => ['required', new ProductCategoryUniqueSlug('products', $this->id)], 'images.files.*' => ['nullable', 'mimes:bmp,jpeg,jpg,png,webp'], @@ -84,10 +99,12 @@ public function rules() } } - foreach ($product->getEditableAttributes() as $attribute) { + $this->productEditableAttributes = $this->product->getEditableAttributes(); + + foreach ($this->productEditableAttributes as $attribute) { if ( in_array($attribute->code, ['sku', 'url_key']) - || $attribute->type == 'boolean' + || $attribute->type == AttributeTypeEnum::BOOLEAN->value ) { continue; } @@ -101,7 +118,7 @@ public function rules() } if ( - $attribute->type == 'text' + $attribute->type == AttributeTypeEnum::TEXT->value && $attribute->validation ) { if ($attribute->validation === 'decimal') { @@ -113,7 +130,7 @@ public function rules() } } - if ($attribute->type == 'price') { + if ($attribute->type == AttributeTypeEnum::PRICE->value) { $validations[] = new Decimal; } @@ -164,4 +181,25 @@ public function attributes() 'variants.*.sku' => 'sku', ]; } + + /** + * Handle a passed validation attempt. + * + * @return void + */ + protected function passedValidation() + { + $tinyMCEFields = $this->productEditableAttributes + ->filter(fn ($attribute) => $attribute->type === AttributeTypeEnum::TEXTAREA->value && $attribute->enable_wysiwyg) + ->pluck('code') + ->toArray(); + + foreach ($tinyMCEFields as $field) { + if ($this->has($field)) { + $this->merge([ + $field => clean_content($this->get($field)), + ]); + } + } + } }
packages/Webkul/Core/src/Http/helpers.php+30 −0 modified@@ -1,5 +1,6 @@ <?php +use Stevebauman\Purify\Facades\Purify; use Webkul\Core\Facades\Acl; use Webkul\Core\Facades\Core; use Webkul\Core\Facades\Menu; @@ -65,6 +66,35 @@ function clean_path(string $path): string } } +if (! function_exists('clean_content')) { + /** + * Clean content. + */ + function clean_content(string $content): string + { + $cleaned = Purify::clean($content); + + $patterns = [ + '/\{\{.*?\}\}/', + '/\{!!.*?!!\}/', + '/@(php|if|else|endif|foreach|endforeach|for|endfor|while|endwhile|switch|endswitch|case|break|continue|include|extends|section|endsection|yield|push|endpush|stack|endstack)/', + '/<\?php.*?\?>/s', + ]; + + foreach ($patterns as $pattern) { + $cleaned = preg_replace($pattern, '', $cleaned); + } + + $cleaned = str_replace( + ['{{', '}}', '{!!', '!!}'], + ['{{', '}}', '{!!', '!!}'], + $cleaned + ); + + return $cleaned; + } +} + if (! function_exists('array_permutation')) { function array_permutation($input) {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.