Moderate severityNVD Advisory· Published Dec 15, 2021· Updated Aug 3, 2024
Cross-site Scripting (XSS) - Stored in yetiforcecompany/yetiforcecrm
CVE-2021-4116
Description
yetiforcecrm is vulnerable to Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
yetiforce/yetiforce-crmPackagist | <= 6.3.0 | — |
Affected products
1- Range: unspecified
Patches
19cdb012ca64fImproved validation of uploaded files
3 files changed · +52 −28
app/Fields/File.php+49 −26 modified@@ -125,6 +125,9 @@ public static function loadFromInfo($fileInfo) foreach ($fileInfo as $key => $value) { $instance->{$key} = $fileInfo[$key]; } + if (isset($instance->name)) { + $instance->name = trim(\App\Purifier::purify($instance->name)); + } return $instance; } @@ -148,14 +151,14 @@ public static function loadFromRequest($file) /** * Load file instance from file path. * - * @param array $path + * @param string $path * * @return \self */ - public static function loadFromPath($path) + public static function loadFromPath(string $path) { $instance = new self(); - $instance->name = basename($path); + $instance->name = trim(\App\Purifier::purify(basename($path))); $instance->path = $path; return $instance; } @@ -202,7 +205,7 @@ public static function loadFromContent(string $contents, $name = false, array $p $name = \App\TextParser::textTruncate($name, 180, false) . '_' . uniqid() . ".$extension"; } $instance = new self(); - $instance->name = $name; + $instance->name = trim(\App\Purifier::purify($name)); $instance->path = $path; $instance->ext = $extension; foreach ($param as $key => $value) { @@ -377,13 +380,13 @@ public function getDirectoryPath() /** * Validate whether the file is safe. * - * @param bool|string $type + * @param string|null $type * * @throws \Exception * * @return bool */ - public function validate($type = false) + public function validate(?string $type = null): bool { $return = true; try { @@ -408,25 +411,25 @@ public function validate($type = false) $message = \call_user_func_array('vsprintf', [\App\Language::translateSingleMod(array_shift($params), 'Other.Exceptions'), $params]); } $this->validateError = $message; - Log::error("Error: {$e->getMessage()} | {$this->getName()} | {$this->getSize()}", __CLASS__); + Log::error("Error during file validation: {$this->getName()} | Size: {$this->getSize()}\n {$e->__toString()}", __CLASS__); } return $return; } /** * Validate and secure the file. * - * @param bool|string $type + * @param string|null $type * * @return bool */ - public function validateAndSecure($type = false): bool + public function validateAndSecure(?string $type = null): bool { if ($this->validate($type)) { return true; } $reValidate = false; - if ('image' === $this->getShortMimeType(0) && static::secureImage($this)) { + if (static::secureFile($this)) { $this->size = filesize($this->path); $this->content = file_get_contents($this->path); $reValidate = true; @@ -480,6 +483,9 @@ private function checkFile() if (empty($this->name)) { throw new \App\Exceptions\DangerousFile('ERR_FILE_EMPTY_NAME'); } + if (!$this->validateInjection($this->name)) { + throw new \App\Exceptions\DangerousFile('ERR_FILE_ILLEGAL_NAME'); + } if (0 === $this->getSize()) { throw new \App\Exceptions\DangerousFile('ERR_FILE_WRONG_SIZE'); } @@ -533,7 +539,7 @@ private function validateCodeInjection() || false !== stripos($contents, '<?=') || false !== stripos($contents, '<? ')) && $this->searchCodeInjection() ) { - throw new \App\Exceptions\DangerousFile('ERR_FILE_PHP_CODE_INJECTION'); + throw new \App\Exceptions\DangerousFile('ERR_FILE_CODE_INJECTION'); } } } @@ -636,43 +642,57 @@ private function searchCodeInjection(): bool */ private function validateCodeInjectionInMetadata() { - if ( + if (\extension_loaded('imagick')) { + try { + $img = new \imagick($this->path); + $this->validateInjection($img->getImageProperties()); + } catch (\Throwable $e) { + throw new \App\Exceptions\DangerousFile('ERR_FILE_CODE_INJECTION', $e->getCode(), $e); + } + } elseif ( \function_exists('exif_read_data') && \in_array($this->getMimeType(), ['image/jpeg', 'image/tiff']) && \in_array(exif_imagetype($this->path), [IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM]) ) { $imageSize = getimagesize($this->path, $imageInfo); - if ( - $imageSize - && (empty($imageInfo['APP1']) || 0 === strpos($imageInfo['APP1'], 'Exif')) - && ($exifdata = exif_read_data($this->path)) && !$this->validateImageMetadata($exifdata) - ) { - throw new \App\Exceptions\DangerousFile('ERR_FILE_PHP_CODE_INJECTION'); + try { + if ( + $imageSize + && (empty($imageInfo['APP1']) || 0 === strpos($imageInfo['APP1'], 'Exif')) + && ($exifData = exif_read_data($this->path)) && !$this->validateInjection($exifData) + ) { + throw new \App\Exceptions\DangerousFile('ERR_FILE_CODE_INJECTION'); + } + } catch (\Throwable $e) { + throw new \App\Exceptions\DangerousFile('ERR_FILE_CODE_INJECTION', $e->getCode(), $e); } } } /** - * Validate image metadata. + * Validate injection. * - * @param mixed $data + * @param string|array $data * * @return bool */ - private function validateImageMetadata($data) + private function validateInjection($data): bool { + $return = true; if (\is_array($data)) { foreach ($data as $value) { - if (!$this->validateImageMetadata($value)) { + if (!$this->validateInjection($value)) { return false; } } } else { if (1 === preg_match('/(<\?php?(.*?))/i', $data) || false !== stripos($data, '<?=') || false !== stripos($data, '<? ')) { - return false; + $return = false; + } else { + \App\Purifier::purifyHtmlEventAttributes($data); } } - return true; + return $return; } /** @@ -1198,7 +1218,7 @@ public static function uploadAndSave(\App\Request $request, array $files, string $additionalNotes = ''; $file = static::loadFromRequest($fileDetails); if (!$file->validate($type)) { - if (!static::secureImage($file)) { + if (!static::secureFile($file)) { $attach[] = ['name' => $file->getName(), 'error' => $file->validateError, 'hash' => $request->getByType('hash', 'Alnum')]; continue; } @@ -1301,8 +1321,11 @@ public static function cleanTemp($keys) * * @return bool */ - public static function secureImage(self $file): bool + public static function secureFile(self $file): bool { + if ('image' !== $file->getShortMimeType(0)) { + return false; + } $result = false; if (\extension_loaded('imagick')) { try {
config/version.php+1 −1 modified@@ -1,7 +1,7 @@ <?php return [ - 'appVersion' => '6.3.5', + 'appVersion' => '6.3.6', 'patchVersion' => '2021.12.14', 'lib_roundcube' => '0.2.3', ];
languages/en-US/Other/Exceptions.json+2 −1 modified@@ -22,8 +22,9 @@ "ERR_FILE_EMPTY_NAME": "File name is empty", "ERR_FILE_ERROR_REQUEST": "File transfer error: %s", "ERR_FILE_ILLEGAL_FORMAT": "Illegal file format", + "ERR_FILE_ILLEGAL_NAME": "Illegal file name", "ERR_FILE_WRONG_IMAGE": "Wrong image file", - "ERR_FILE_PHP_CODE_INJECTION": "File contains dangerous PHP code and was rejected", + "ERR_FILE_CODE_INJECTION": "File contains dangerous code and was rejected", "ERR_FILE_XPACKET_CODE_INJECTION": "File contains dangerous XPACKET code and was rejected", "ERR_NO_PERMISSIONS_TO_CREATE_DIRECTORIES": "No permissions to create directories", "ERR_DIRECTORY_CANNOT_BE_CREATED": "The directory cannot be created",
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- github.com/advisories/GHSA-fwh7-v4gf-xv7wghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-4116ghsaADVISORY
- github.com/yetiforcecompany/yetiforcecrm/commit/9cdb012ca64ff1f719f8120d5fd162cd5ef1013fghsax_refsource_MISCWEB
- huntr.dev/bounties/7561bae7-9053-4dc8-aa59-b71acfb1712cghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.