VYPR
Moderate severityNVD Advisory· Published May 5, 2022· Updated Aug 3, 2024

Unrestructed file upload in yetiforcecompany/yetiforcecrm

CVE-2022-1411

Description

Unrestructed file upload in GitHub repository yetiforcecompany/yetiforcecrm prior to 6.4.0. Attacker can send malicious files to the victims is able to retrieve the stored data from the web application without that data being made safe to render in the browser and steals victim's cookie leads to account takeover.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
yetiforce/yetiforce-crmPackagist
< 6.4.06.4.0

Affected products

1

Patches

1
bf69c4272600

Added validation to pasted files to the wysiwyg editor

https://github.com/yetiforcecompany/yetiforcecrmMariusz KrzaczkowskiMay 5, 2022via ghsa
6 files changed · +427 234
  • app/Fields/File.php+35 22 modified
    @@ -166,6 +166,39 @@ public static function loadFromPath(string $path)
     		return $instance;
     	}
     
    +	/**
    +	 * Load file instance from base string.
    +	 *
    +	 * @param string $contents
    +	 * @param array  $param
    +	 *
    +	 * @return \self|null
    +	 */
    +	public static function loadFromBase(string $contents, array $param = []): ?self
    +	{
    +		$result = explode(',', $contents, 2);
    +		$contentType = $isBase64 = false;
    +		if (2 === \count($result)) {
    +			[$metadata, $data] = $result;
    +			foreach (explode(';', $metadata) as $cur) {
    +				if ('base64' === $cur) {
    +					$isBase64 = true;
    +				} elseif ('data:' === substr($cur, 0, 5)) {
    +					$contentType = str_replace('data:', '', $cur);
    +				}
    +			}
    +		} else {
    +			$data = $result[0];
    +		}
    +		$data = rawurldecode($data);
    +		$rawData = $isBase64 ? base64_decode($data) : $data;
    +		if (\strlen($rawData) < 12) {
    +			Log::error('Incorrect content value: ' . $contents, __CLASS__);
    +			return null;
    +		}
    +		return static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType]));
    +	}
    +
     	/**
     	 * Load file instance from content.
     	 *
    @@ -881,27 +914,7 @@ public static function getMimeContentType($fileName)
     	 */
     	public static function saveFromString(string $contents, array $param = [])
     	{
    -		$result = explode(',', $contents, 2);
    -		$contentType = $isBase64 = false;
    -		if (2 === \count($result)) {
    -			[$metadata, $data] = $result;
    -			foreach (explode(';', $metadata) as $cur) {
    -				if ('base64' === $cur) {
    -					$isBase64 = true;
    -				} elseif ('data:' === substr($cur, 0, 5)) {
    -					$contentType = str_replace('data:', '', $cur);
    -				}
    -			}
    -		} else {
    -			$data = $result[0];
    -		}
    -		$data = rawurldecode($data);
    -		$rawData = $isBase64 ? base64_decode($data) : $data;
    -		if (\strlen($rawData) < 12) {
    -			Log::error('Incorrect content value: ' . $contents, __CLASS__);
    -			return false;
    -		}
    -		$fileInstance = static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType]));
    +		$fileInstance = static::loadFromBase($contents, $param);
     		if ($fileInstance->validateAndSecure()) {
     			return $fileInstance;
     		}
    @@ -1230,7 +1243,7 @@ public function insertTempFile(array $params): int
     			'createdtime' => date('Y-m-d H:i:s'),
     			'fieldname' => null,
     			'key' => null,
    -			'crmid' => 0
    +			'crmid' => 0,
     		];
     		foreach ($data as $key => &$value) {
     			if (isset($params[$key])) {
    
  • app/Validator.php+16 0 modified
    @@ -483,4 +483,20 @@ public static function path(string $input): bool
     			return !self::dirName($dir);
     		});
     	}
    +
    +	/**
    +	 * Check base64.
    +	 *
    +	 * @param string $input
    +	 *
    +	 * @return bool
    +	 */
    +	public static function base64(string $input): bool
    +	{
    +		if (empty($input)) {
    +			return false;
    +		}
    +		$explode = explode(',', $input);
    +		return 2 === \count($explode) && 1 === preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $explode[1]);
    +	}
     }
    
  • config/version.php+1 1 modified
    @@ -1,7 +1,7 @@
     <?php
     
     return [
    -	'appVersion' => '6.3.220',
    +	'appVersion' => '6.3.221',
     	'patchVersion' => '2022.05.05',
     	'lib_roundcube' => '0.2.12',
     ];
    
  • modules/Vtiger/actions/Fields.php+35 8 modified
    @@ -59,6 +59,7 @@ public function __construct()
     		$this->exposeMethod('validateByMode');
     		$this->exposeMethod('verifyPhoneNumber');
     		$this->exposeMethod('changeFavoriteOwner');
    +		$this->exposeMethod('validateFile');
     	}
     
     	/**
    @@ -68,7 +69,7 @@ public function __construct()
     	 *
     	 * @throws \App\Exceptions\NoPermitted
     	 */
    -	public function getOwners(App\Request $request)
    +	public function getOwners(App\Request $request): void
     	{
     		if (!App\Config::performance('SEARCH_OWNERS_BY_AJAX')) {
     			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
    @@ -121,7 +122,7 @@ public function getOwners(App\Request $request)
     	 *
     	 * @throws \App\Exceptions\NoPermitted
     	 */
    -	public function getUserRole(App\Request $request)
    +	public function getUserRole(App\Request $request): void
     	{
     		if (!App\Config::performance('SEARCH_ROLES_BY_AJAX')) {
     			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
    @@ -148,7 +149,7 @@ public function getUserRole(App\Request $request)
     	 *
     	 * @throws \App\Exceptions\NoPermitted
     	 */
    -	public function getReference(App\Request $request)
    +	public function getReference(App\Request $request): void
     	{
     		if ($request->has('fieldName')) {
     			$fieldModel = Vtiger_Module_Model::getInstance($request->getModule())->getFieldByName($request->getByType('fieldName', 2));
    @@ -195,7 +196,7 @@ public function getReference(App\Request $request)
     	 *
     	 * @throws \App\Exceptions\NoPermitted
     	 */
    -	public function verifyPhoneNumber(App\Request $request)
    +	public function verifyPhoneNumber(App\Request $request): void
     	{
     		if ('phone' !== $this->fieldModel->getFieldDataType()) {
     			throw new \App\Exceptions\NoPermitted('ERR_NO_PERMISSIONS_TO_FIELD');
    @@ -224,7 +225,7 @@ public function verifyPhoneNumber(App\Request $request)
     	 *
     	 * @param \App\Request $request
     	 */
    -	public function findAddress(App\Request $request)
    +	public function findAddress(App\Request $request): void
     	{
     		$instance = \App\Map\Address::getInstance($request->getByType('type'));
     		$response = new Vtiger_Response();
    @@ -243,7 +244,7 @@ public function findAddress(App\Request $request)
     	 * @throws \App\Exceptions\NoPermitted
     	 * @throws \yii\db\Exception
     	 */
    -	public function changeFavoriteOwner(App\Request $request)
    +	public function changeFavoriteOwner(App\Request $request): void
     	{
     		if (!App\Config::module('Users', 'FAVORITE_OWNERS') || (\App\User::getCurrentUserRealId() !== \App\User::getCurrentUserId())) {
     			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
    @@ -265,7 +266,7 @@ public function changeFavoriteOwner(App\Request $request)
     	 *
     	 * @throws \App\Exceptions\NoPermitted
     	 */
    -	public function validateForField(App\Request $request)
    +	public function validateForField(App\Request $request): void
     	{
     		$fieldModel = Vtiger_Module_Model::getInstance($request->getModule())->getFieldByName($request->getByType('fieldName', 2));
     		if (!$fieldModel || !$fieldModel->isActiveField() || !$fieldModel->isViewEnabled()) {
    @@ -288,7 +289,7 @@ public function validateForField(App\Request $request)
     	 *
     	 * @throws \App\Exceptions\NoPermitted
     	 */
    -	public function validateByMode(App\Request $request)
    +	public function validateByMode(App\Request $request): void
     	{
     		if ($request->isEmpty('purifyMode') || !$request->has('value')) {
     			throw new \App\Exceptions\NoPermitted('ERR_ILLEGAL_VALUE', 406);
    @@ -299,4 +300,30 @@ public function validateByMode(App\Request $request)
     		]);
     		$response->emit();
     	}
    +
    +	/**
    +	 * Validate file.
    +	 *
    +	 * @param App\Request $request
    +	 *
    +	 * @return void
    +	 */
    +	public function validateFile(App\Request $request): void
    +	{
    +		$validate = false;
    +		if ($request->has('base64')) {
    +			$fileInstance = \App\Fields\File::loadFromBase($request->getByType('base64', 'base64'), ['validateAllowedFormat' => 'image']);
    +			if ($fileInstance && $fileInstance->validate()) {
    +				$validate = true;
    +			} else {
    +				$validateError = $fileInstance->validateError;
    +			}
    +		}
    +		$response = new Vtiger_Response();
    +		$response->setResult([
    +			'validate' => $validate,
    +			'validateError' => $validateError ?? null,
    +		]);
    +		$response->emit();
    +	}
     }
    
  • public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js+225 134 modified
    @@ -1,21 +1,24 @@
    -/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
    +/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
    +'use strict';
     
     CKEDITOR.dialog.add('base64image-dialog', function (editor) {
    -	var t = null,
    +	let self = null,
     		selectedImg = null,
     		orgWidth = null,
     		orgHeight = null,
     		imgPreview = null,
    -		imgScal = 1,
    -		lock = true;
    +		sourceElements = [],
    +		imgScale = 1,
    +		lock = true,
    +		maxUploadSize = CONFIG['maxUploadLimit'];
     
     	/* Check File Reader Support */
     	function fileSupport() {
    -		var r = false,
    +		let r = false,
     			n = null;
     		try {
     			if (FileReader) {
    -				var n = document.createElement('input');
    +				let n = document.createElement('input');
     				if (n && 'files' in n) r = true;
     			}
     		} catch (e) {
    @@ -24,7 +27,7 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     		n = null;
     		return r;
     	}
    -	var isFReaderSupported = fileSupport();
    +	let isFReaderSupported = fileSupport();
     
     	/* Load preview image */
     	function imagePreviewLoad(s) {
    @@ -35,10 +38,10 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     		}
     
     		/* Create image */
    -		var i = new Image();
    +		let i = new Image();
     
     		/* Display loading text in preview element */
    -		imgPreview.getElement().setHtml('Loading...');
    +		$(imgPreview.getElement().$).progressIndicator({ position: 'html' });
     
     		/* When image is loaded */
     		i.onload = function () {
    @@ -47,11 +50,11 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     
     			/* Set attributes */
     			if (orgWidth == null || orgHeight == null) {
    -				t.setValueOf('tab-properties', 'width', this.width);
    -				t.setValueOf('tab-properties', 'height', this.height);
    -				imgScal = 1;
    -				if (this.height > 0 && this.width > 0) imgScal = this.width / this.height;
    -				if (imgScal <= 0) imgScal = 1;
    +				self.setValueOf('tab-properties', 'width', this.width);
    +				self.setValueOf('tab-properties', 'height', this.height);
    +				imgScale = 1;
    +				if (this.height > 0 && this.width > 0) imgScale = this.width / this.height;
    +				if (imgScale <= 0) imgScale = 1;
     			} else {
     				orgWidth = null;
     				orgHeight = null;
    @@ -62,7 +65,7 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     
     			/* Insert preview image */
     			try {
    -				var p = imgPreview.getElement().$;
    +				let p = imgPreview.getElement().$;
     				if (p) p.appendChild(this);
     			} catch (e) {}
     		};
    @@ -82,39 +85,61 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     	function imagePreview(src) {
     		imgPreview.getElement().setHtml('');
     		if (isFReaderSupported) {
    -			var fileI = t.getContentElement('tab-source', 'file');
    -			var n = null;
    -			try {
    -				n = fileI.getInputElement().$;
    -			} catch (e) {
    -				n = null;
    -			}
    -			if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) {
    -				if ('type' in n.files[0] && !n.files[0].type.match('image.*')) return;
    -				if (!FileReader) return;
    -				imgPreview.getElement().setHtml('Loading...');
    -				var fr = new FileReader();
    -				fr.onload = (function (f) {
    -					return function (e) {
    -						imgPreview.getElement().setHtml('');
    -						imagePreviewLoad(e.target.result);
    -					};
    -				})(n.files[0]);
    -				fr.onerror = function () {
    +			$(imgPreview.getElement().$).progressIndicator({ position: 'html' });
    +			readImageAsBase64()
    +				.done(function (base) {
     					imgPreview.getElement().setHtml('');
    -				};
    -				fr.onabort = function () {
    +					imagePreviewLoad(base);
    +				})
    +				.fail(function () {
     					imgPreview.getElement().setHtml('');
    -				};
    -				fr.readAsDataURL(n.files[0]);
    +				});
    +		}
    +	}
    +
    +	function readImageAsBase64() {
    +		const aDeferred = jQuery.Deferred();
    +		let fileI = self.getContentElement('tab-source', 'file'),
    +			n = null;
    +		try {
    +			n = fileI.getInputElement().$;
    +		} catch (e) {
    +			n = null;
    +		}
    +		if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) {
    +			if (('type' in n.files[0] && !n.files[0].type.match('image.*')) || !FileReader) {
    +				aDeferred.reject();
    +				return aDeferred.promise();
    +			}
    +			if (n.files[0].size > maxUploadSize) {
    +				app.showNotify({
    +					text: app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'),
    +					type: 'error'
    +				});
    +				aDeferred.reject();
    +				return aDeferred.promise();
     			}
    +			let fr = new FileReader();
    +			fr.onload = (function (f) {
    +				return function (e) {
    +					aDeferred.resolve(e.target.result);
    +				};
    +			})(n.files[0]);
    +			fr.onerror = function () {
    +				aDeferred.reject();
    +			};
    +			fr.onabort = function () {
    +				aDeferred.reject();
    +			};
    +			fr.readAsDataURL(n.files[0]);
     		}
    +		return aDeferred.promise();
     	}
     
     	function getImageDimensions() {
    -		var o = {
    -			w: t.getContentElement('tab-properties', 'width').getValue(),
    -			h: t.getContentElement('tab-properties', 'height').getValue(),
    +		let o = {
    +			w: self.getContentElement('tab-properties', 'width').getValue(),
    +			h: self.getContentElement('tab-properties', 'height').getValue(),
     			uw: 'px',
     			uh: 'px'
     		};
    @@ -128,34 +153,81 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     	}
     
     	function imageDimensions(src) {
    -		var o = getImageDimensions();
    -		var u = 'px';
    +		let o = getImageDimensions();
    +		let u = 'px';
     		if (src == 'width') {
     			if (o.uw == '%') u = '%';
    -			o.h = Math.round(o.w / imgScal);
    +			o.h = Math.round(o.w / imgScale);
     		} else {
     			if (o.uh == '%') u = '%';
    -			o.w = Math.round(o.h * imgScal);
    +			o.w = Math.round(o.h * imgScale);
     		}
     		if (u == '%') {
     			o.w += '%';
     			o.h += '%';
     		}
    -		t.getContentElement('tab-properties', 'width').setValue(o.w),
    -			t.getContentElement('tab-properties', 'height').setValue(o.h);
    +		self.getContentElement('tab-properties', 'width').setValue(o.w),
    +			self.getContentElement('tab-properties', 'height').setValue(o.h);
     	}
     
     	function integerValue(elem) {
    -		var v = elem.getValue(),
    +		let v = elem.getValue(),
     			u = '';
     		if (v.indexOf('%') >= 0) u = '%';
     		v = parseInt(v, 10);
     		if (isNaN(v)) v = 0;
     		elem.setValue(v + u);
     	}
     
    +	function validateFile() {
    +		const fieldInfo = $(editor.element.$).data('fieldinfo');
    +		let length = editor.getData().length,
    +			selectedImg = editor.getSelection();
    +		if (selectedImg) selectedImg = selectedImg.getSelectedElement();
    +		if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
    +		if (selectedImg) {
    +			length = length - selectedImg.getOuterHtml().length;
    +		}
    +		const aDeferred = jQuery.Deferred();
    +		readImageAsBase64()
    +			.done((base) => {
    +				length += base.length;
    +				if (length > fieldInfo['maximumlength']) {
    +					app.showNotify({
    +						text: app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength'],
    +						type: 'error'
    +					});
    +				}
    +				AppConnector.request({
    +					module: app.getModuleName(),
    +					action: 'Fields',
    +					mode: 'validateFile',
    +					fieldName: fieldInfo['name'],
    +					base64: base
    +				})
    +					.done((data) => {
    +						if (data.result.validate) {
    +							aDeferred.resolve();
    +						} else {
    +							app.showNotify({
    +								text: data.result.validateError,
    +								type: 'error'
    +							});
    +							aDeferred.reject();
    +						}
    +					})
    +					.fail(function () {
    +						aDeferred.resolve();
    +					});
    +			})
    +			.fail(() => {
    +				aDeferred.reject();
    +			});
    +		return aDeferred.promise();
    +	}
    +
     	if (isFReaderSupported) {
    -		var sourceElements = [
    +		sourceElements = [
     			{
     				type: 'hbox',
     				widths: ['70px'],
    @@ -165,8 +237,16 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     						type: 'file',
     						id: 'file',
     						label: '',
    -						onChange: function () {
    -							imagePreview('file');
    +						size: maxUploadSize,
    +						onChange: function (a) {
    +							validateFile()
    +								.done(() => {
    +									imagePreview('file');
    +								})
    +								.fail(function () {
    +									self.getContentElement('tab-source', 'file').getInputElement().$.value = null;
    +									imgPreview.getElement().setHtml('');
    +								});
     						}
     					}
     				]
    @@ -240,22 +320,25 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     				);
     		},
     		onShow: function () {
    +			this.getContentElement('tab-source', 'file')
    +				.getInputElement()
    +				.$.setAttribute('accept', 'image/jpeg, image/png, image/gif');
     			/* Remove preview */
     			imgPreview.getElement().setHtml('');
     
    -			(t = this), (orgWidth = null), (orgHeight = null), (imgScal = 1), (lock = true);
    +			(self = this), (orgWidth = null), (orgHeight = null), (imgScale = 1), (lock = true);
     
     			/* selected image or null */
     			selectedImg = editor.getSelection();
     			if (selectedImg) selectedImg = selectedImg.getSelectedElement();
     			if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
     
     			/* Set input values */
    -			t.setValueOf('tab-properties', 'lock', lock);
    -			t.setValueOf('tab-properties', 'vmargin', '0');
    -			t.setValueOf('tab-properties', 'hmargin', '0');
    -			t.setValueOf('tab-properties', 'border', '0');
    -			t.setValueOf('tab-properties', 'align', 'none');
    +			self.setValueOf('tab-properties', 'lock', lock);
    +			self.setValueOf('tab-properties', 'vmargin', '0');
    +			self.setValueOf('tab-properties', 'hmargin', '0');
    +			self.setValueOf('tab-properties', 'border', '0');
    +			self.setValueOf('tab-properties', 'align', 'none');
     			if (selectedImg) {
     				/* Set input values from selected image */
     				if (typeof selectedImg.getAttribute('width') == 'string') orgWidth = selectedImg.getAttribute('width');
    @@ -265,13 +348,13 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     					orgHeight = selectedImg.$.height;
     				}
     				if (orgWidth != null && orgHeight != null) {
    -					t.setValueOf('tab-properties', 'width', orgWidth);
    -					t.setValueOf('tab-properties', 'height', orgHeight);
    +					self.setValueOf('tab-properties', 'width', orgWidth);
    +					self.setValueOf('tab-properties', 'height', orgHeight);
     					orgWidth = parseInt(orgWidth, 10);
     					orgHeight = parseInt(orgHeight, 10);
    -					imgScal = 1;
    -					if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScal = orgWidth / orgHeight;
    -					if (imgScal <= 0) imgScal = 1;
    +					imgScale = 1;
    +					if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScale = orgWidth / orgHeight;
    +					if (imgScale <= 0) imgScale = 1;
     				}
     
     				if (typeof selectedImg.getAttribute('src') == 'string') {
    @@ -281,116 +364,124 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
     					}
     				}
     				if (typeof selectedImg.getAttribute('alt') == 'string')
    -					t.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt'));
    +					self.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt'));
     				if (typeof selectedImg.getAttribute('hspace') == 'string')
    -					t.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace'));
    +					self.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace'));
     				if (typeof selectedImg.getAttribute('vspace') == 'string')
    -					t.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace'));
    +					self.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace'));
     				if (typeof selectedImg.getAttribute('border') == 'string')
    -					t.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border'));
    +					self.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border'));
     				if (typeof selectedImg.getAttribute('align') == 'string') {
     					switch (selectedImg.getAttribute('align')) {
     						case 'top':
     						case 'text-top':
    -							t.setValueOf('tab-properties', 'align', 'top');
    +							self.setValueOf('tab-properties', 'align', 'top');
     							break;
     						case 'baseline':
     						case 'bottom':
     						case 'text-bottom':
    -							t.setValueOf('tab-properties', 'align', 'bottom');
    +							self.setValueOf('tab-properties', 'align', 'bottom');
     							break;
     						case 'left':
    -							t.setValueOf('tab-properties', 'align', 'left');
    +							self.setValueOf('tab-properties', 'align', 'left');
     							break;
     						case 'right':
    -							t.setValueOf('tab-properties', 'align', 'right');
    +							self.setValueOf('tab-properties', 'align', 'right');
     							break;
     					}
     				}
    -				t.selectPage('tab-properties');
    +				self.selectPage('tab-properties');
     			}
     		},
     		onOk: function () {
     			/* Get image source */
    -			var src = '';
    +			let src = '';
     			try {
     				src = CKEDITOR.document.getById(editor.id + 'previewimage').$.src;
     			} catch (e) {
     				src = '';
     			}
     			if (typeof src != 'string' || src == null || src === '') return;
     
    -			/* selected image or new image */
    -			if (selectedImg) var newImg = selectedImg;
    -			else var newImg = editor.document.createElement('img');
    -			newImg.setAttribute('src', src);
    -			src = null;
    +			validateFile().always(() => {
    +				/* selected image or new image */
    +				if (selectedImg) {
    +					var newImg = selectedImg;
    +				} else {
    +					var newImg = editor.document.createElement('img');
    +				}
    +				newImg.setAttribute('src', src);
    +				src = null;
     
    -			/* Set attributes */
    -			newImg.setAttribute('alt', t.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, ''));
    -			var attr = {
    -					width: ['width', 'width:#;', 'integer', 1],
    -					height: ['height', 'height:#;', 'integer', 1],
    -					vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0],
    -					hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0],
    -					align: ['align', ''],
    -					border: ['border', 'border:# solid black;', 'integer', 0]
    -				},
    -				css = [],
    -				value,
    -				cssvalue,
    -				attrvalue,
    -				k;
    -			for (k in attr) {
    -				value = t.getValueOf('tab-properties', k);
    -				attrvalue = value;
    -				cssvalue = value;
    -				unit = 'px';
    -
    -				if (k == 'align') {
    -					switch (value) {
    -						case 'top':
    -						case 'bottom':
    -							attr[k][1] = 'vertical-align:#;';
    -							break;
    -						case 'left':
    -						case 'right':
    -							attr[k][1] = 'float:#;';
    -							break;
    -						default:
    -							value = null;
    -							break;
    +				/* Set attributes */
    +				newImg.setAttribute('alt', self.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, ''));
    +				let attr = {
    +						width: ['width', 'width:#;', 'integer', 1],
    +						height: ['height', 'height:#;', 'integer', 1],
    +						vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0],
    +						hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0],
    +						align: ['align', ''],
    +						border: ['border', 'border:# solid black;', 'integer', 0]
    +					},
    +					css = [],
    +					value,
    +					cssValue,
    +					attrValue,
    +					unit,
    +					k;
    +				for (k in attr) {
    +					value = self.getValueOf('tab-properties', k);
    +					attrValue = value;
    +					cssValue = value;
    +					unit = 'px';
    +
    +					if (k == 'align') {
    +						switch (value) {
    +							case 'top':
    +							case 'bottom':
    +								attr[k][1] = 'vertical-align:#;';
    +								break;
    +							case 'left':
    +							case 'right':
    +								attr[k][1] = 'float:#;';
    +								break;
    +							default:
    +								value = null;
    +								break;
    +						}
     					}
    -				}
     
    -				if (attr[k][2] == 'integer') {
    -					if (value.indexOf('%') >= 0) unit = '%';
    -					value = parseInt(value, 10);
    -					if (isNaN(value)) value = null;
    -					else if (value < attr[k][3]) value = null;
    -					if (value != null) {
    -						if (unit == '%') {
    -							attrvalue = value + '%';
    -							cssvalue = value + '%';
    -						} else {
    -							attrvalue = value;
    -							cssvalue = value + 'px';
    +					if (attr[k][2] == 'integer') {
    +						if (value.indexOf('%') >= 0) unit = '%';
    +						value = parseInt(value, 10);
    +						if (isNaN(value)) value = null;
    +						else if (value < attr[k][3]) value = null;
    +						if (value != null) {
    +							if (unit == '%') {
    +								attrValue = value + '%';
    +								cssValue = value + '%';
    +							} else {
    +								attrValue = value;
    +								cssValue = value + 'px';
    +							}
     						}
     					}
    -				}
     
    -				if (value != null) {
    -					newImg.setAttribute(attr[k][0], attrvalue);
    -					css.push(attr[k][1].replace(/#/g, cssvalue));
    +					if (value != null) {
    +						newImg.setAttribute(attr[k][0], attrValue);
    +						css.push(attr[k][1].replace(/#/g, cssValue));
    +					}
     				}
    -			}
    -			if (css.length > 0) newImg.setAttribute('style', css.join(''));
    +				if (css.length > 0) newImg.setAttribute('style', css.join(''));
    +
    +				/* Insert new image */
    +				if (!selectedImg) editor.insertElement(newImg);
     
    -			/* Insert new image */
    -			if (!selectedImg) editor.insertElement(newImg);
    +				/* Resize image */
    +				if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800);
     
    -			/* Resize image */
    -			if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800);
    +				editor.updateElement();
    +			});
     		},
     
     		/* Dialog form */
    
  • public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js+115 69 modified
    @@ -1,72 +1,28 @@
    -/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
    -
    -function initPasteEvent(editorInstance) {
    -	if (editorInstance.addFeature) {
    -		editorInstance.addFeature({
    -			allowedContent: 'img[alt,id,!src]{width,height};'
    -		});
    -	}
    -
    -	editorInstance.on('contentDom', function () {
    -		var editableElement = editorInstance.editable ? editorInstance.editable() : editorInstance.document;
    -		editableElement.on('paste', onPaste, null, { editor: editorInstance });
    -	});
    -}
    -function onPaste(event) {
    -	var editor = event.listenerData && event.listenerData.editor;
    -	var $event = event.data.$;
    -	var clipboardData = $event.clipboardData;
    -	var found = false;
    -	var imageType = /^image/;
    -
    -	if (!clipboardData) {
    -		return;
    -	}
    -	return Array.prototype.forEach.call(clipboardData.types, function (type, i) {
    -		if (found) {
    -			return;
    -		}
    -		if (type.match(imageType) || clipboardData.items[i].type.match(imageType)) {
    -			readImageAsBase64(clipboardData.items[i], editor);
    -			return (found = true);
    -		}
    -	});
    -}
    -
    -function readImageAsBase64(item, editor) {
    -	if (!item || typeof item.getAsFile !== 'function') {
    -		return;
    -	}
    -	var file = item.getAsFile();
    -	var reader = new FileReader();
    -	reader.onload = function (evt) {
    -		var element = editor.document.createElement('img', {
    -			attributes: {
    -				src: evt.target.result
    -			}
    -		});
    -		setTimeout(function () {
    -			editor.insertElement(element);
    -		}, 10);
    -	};
    -	reader.readAsDataURL(file);
    -}
    +/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
    +'use strict';
     
     CKEDITOR.plugins.add('base64image', {
     	requires: 'dialog',
     	icons: 'base64image',
     	hidpi: true,
    -	init: function (editorInstance) {
    -		initPasteEvent(editorInstance);
    -		var pluginName = 'base64image-dialog';
    -		editorInstance.ui.addToolbarGroup('base64image', 'insert');
    -		editorInstance.ui.addButton('base64image', {
    -			label: editorInstance.lang.common.image,
    +	init: function (editor) {
    +		if (editor.addFeature) {
    +			editor.addFeature({
    +				allowedContent: 'img[alt,id,!src]{width,height};'
    +			});
    +		}
    +		editor.on('paste', (event, a, b) => {
    +			this.onPaste(event);
    +		});
    +		const pluginName = 'base64image-dialog';
    +		editor.ui.addToolbarGroup('base64image', 'insert');
    +		editor.ui.addButton('base64image', {
    +			label: editor.lang.common.image,
     			command: pluginName,
     			toolbar: 'insert'
     		});
     		CKEDITOR.dialog.add(pluginName, this.path + 'dialogs/dialog.js');
    -		editorInstance.addCommand(
    +		editor.addCommand(
     			pluginName,
     			new CKEDITOR.dialogCommand(pluginName, {
     				allowedContent:
    @@ -78,29 +34,119 @@ CKEDITOR.plugins.add('base64image', {
     				]
     			})
     		);
    -		editorInstance.on('doubleclick', function (evt) {
    +		editor.on('doubleclick', function (evt) {
     			if (evt.data.element && !evt.data.element.isReadOnly() && evt.data.element.getName() === 'img') {
     				evt.data.dialog = pluginName;
    -				editorInstance.getSelection().selectElement(evt.data.element);
    +				editor.getSelection().selectElement(evt.data.element);
     			}
     		});
    -		if (editorInstance.addMenuItem) {
    -			editorInstance.addMenuGroup('imageToBase64Group');
    -			editorInstance.addMenuItem('imageToBase64Item', {
    -				label: editorInstance.lang.common.image,
    +		if (editor.addMenuItem) {
    +			editor.addMenuGroup('imageToBase64Group');
    +			editor.addMenuItem('imageToBase64Item', {
    +				label: editor.lang.common.image,
     				icon: this.path + 'icons/base64image.png',
     				command: pluginName,
     				group: 'imageToBase64Group'
     			});
     		}
    -		if (editorInstance.contextMenu) {
    -			editorInstance.contextMenu.addListener(function (element, selection) {
    +		if (editor.contextMenu) {
    +			editor.contextMenu.addListener(function (element) {
     				if (element && element.getName() === 'img') {
    -					editorInstance.getSelection().selectElement(element);
    +					editor.getSelection().selectElement(element);
     					return { imageToBase64Item: CKEDITOR.TRISTATE_ON };
     				}
     				return null;
     			});
     		}
    +	},
    +	onPaste: function (event) {
    +		const self = this,
    +			allowedTypes = 'image/jpeg|image/png|image/gif',
    +			dataTransfer = event.data.dataTransfer,
    +			editor = event.editor,
    +			count = dataTransfer.getFilesCount();
    +		for (let index = 0; index < count; index++) {
    +			let file = dataTransfer.getFile(index);
    +			if (file.type.match(allowedTypes)) {
    +				self
    +					.validateFile(file, editor)
    +					.done(function (base) {
    +						let image,
    +							selectedImg = editor.getSelection();
    +						if (selectedImg) selectedImg = selectedImg.getSelectedElement();
    +						if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
    +						if (selectedImg) {
    +							image = selectedImg;
    +						} else {
    +							image = editor.document.createElement('img');
    +						}
    +						image.setAttribute('src', base);
    +						if (!selectedImg) editor.insertElement(image);
    +					})
    +					.fail(function (error) {
    +						editor.showNotification(error, 'warning');
    +					});
    +			} else {
    +				editor.showNotification(
    +					app.vtranslate('JS_INVALID_FILE_TYPE') +
    +						'<br>' +
    +						app.vtranslate('JS_AVAILABLE_FILE_TYPES') +
    +						': ' +
    +						allowedTypes.replace(/\|/g, ', '),
    +					'warning'
    +				);
    +			}
    +		}
    +	},
    +	validateFile: function (file, editor) {
    +		const aDeferred = jQuery.Deferred();
    +		if (file.size > CONFIG['maxUploadLimit']) {
    +			aDeferred.reject(app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'));
    +		}
    +		this.readAndValidate(file, editor)
    +			.done(function (base) {
    +				aDeferred.resolve(base);
    +			})
    +			.fail(function (error) {
    +				aDeferred.reject(error);
    +			});
    +		return aDeferred.promise();
    +	},
    +	readAndValidate: function (file, editor) {
    +		const aDeferred = jQuery.Deferred(),
    +			fieldInfo = $(editor.element.$).data('fieldinfo');
    +		let length = editor.getData().length,
    +			selectedImg = editor.getSelection();
    +		if (selectedImg) selectedImg = selectedImg.getSelectedElement();
    +		if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
    +		if (selectedImg) {
    +			length = length - selectedImg.getOuterHtml().length;
    +		}
    +		const fileReader = new FileReader();
    +		fileReader.onload = function (evt) {
    +			length += evt.target.result.length;
    +			if (length > fieldInfo['maximumlength']) {
    +				return aDeferred.reject(app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength']);
    +			}
    +			AppConnector.request({
    +				module: app.getModuleName(),
    +				action: 'Fields',
    +				mode: 'validateFile',
    +				fieldName: fieldInfo['name'],
    +				base64: evt.target.result
    +			})
    +				.done((data) => {
    +					if (data.result.validate) {
    +						aDeferred.resolve(evt.target.result);
    +					} else {
    +						aDeferred.reject(data.result.validateError);
    +					}
    +				})
    +				.fail(function () {
    +					aDeferred.reject();
    +				});
    +		};
    +		fileReader.readAsDataURL(file);
    +		return aDeferred.promise();
     	}
     });
    

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

4

News mentions

0

No linked articles in our index yet.