CVE-2023-1034 (PHP, published 2023-02-25T02:15Z)
Description: value: Path Traversal: '\..\filename' in GitHub repository salesagility/suitecrm prior to 7.12.9.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-29 \u2014 Path Traversal: '\..\filename'
The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize '\..\filename' (leading backslash dot dot) sequences that can resolve to a location that is outside of that directory.
Repository: https://github.com/salesagility/suitecrm
Files changed:
Api/V8/JsonApi/Helper/AttributeObjectHelper.php
README.md
data/BeanFactory.php
files.md5
include/InlineEditing/InlineEditing.php
include/MySugar/MySugar.php
include/utils.php
install/install_utils.php
lib/API/JsonApi/v1/Resource/SuiteBeanResource.php
modules/AOS_Products/AOS_Products.php
modules/Administration/UpgradeWizard.php
modules/Campaigns/WebToPersonCapture.php
modules/Campaigns/utils.php
modules/Contacts/ContactFormBase.php
modules/EmailTemplates/AttachFiles.php
modules/Emails/include/ComposeView/ComposeView.tpl
modules/Employees/Employee.php
modules/Leads/LeadFormBase.php
modules/Prospects/ProspectFormBase.php
modules/Users/User.php
suitecrm_version.php
Commit message: SuiteCRM 7.12.9 Release


Vulnerable code:
        if ($fields !== null) {
            $attributes = array_intersect_key($attributes, array_flip($fields));
# SuiteCRM 7.12.8
// created: 2022-11-10 17:00:00
  './Api/V8/JsonApi/Helper/AttributeObjectHelper.php' => '8c2e2f889cefdc7912fd62da3e6a6f71',
  './README.md' => '76ec8409ea1fa360d1255f693dfd5a40',
  './data/BeanFactory.php' => '84b7c36b6a59ea8c5c4069659cc72950',
  './include/EditView/EditView2.php' => 'b2684fe27ec214563d935d5080dd9742',
  './include/InlineEditing/InlineEditing.php' => '556f91cf67cb17620fb42a5bd607b7a6',
  './include/MySugar/MySugar.php' => 'c047f36230f6e720537e546ab32e4190',
  './include/SugarTinyMCE.php' => '3e1df6fa7bcbb9e3caf75d97e1d8924b',
  './include/database/MysqliManager.php' => '31cf1de8eba53e933ea116870129da12',
  './include/language/en_us.lang.php' => '6963637150f1b221f9f70dd45d63bf87',
  './include/utils.php' => '55b02dfc0dc39c73996597a598b96264',
  './install/install_utils.php' => '9910d3f9f63ac7fafd1ed474f8a773b1',
  './lib/API/JsonApi/v1/Resource/SuiteBeanResource.php' => '70039bac401d7af441505cd9f78a9dd9',
  './lib/Search/ElasticSearch/ElasticSearchEngine.php' => '89b2b27ef2c5dd8e7482360a8e499bfc',
  './lib/Search/ElasticSearch/ElasticSearchIndexer.php' => '580d41b2ebfb016fff416f9cea38dd57',
  './lib/Search/SearchQuery.php' => 'ccc5b48e33c5bd4c1cc51be8f5889466',
  './lib/Search/UI/SearchResultsController.php' => '4670567daea7e976e25cd215150f79ed',
  './lib/Utility/ArrayMapper.php' => '128538045d9530d999a46b90ed14c3c1',
  './modules/AOS_Products/AOS_Products.php' => 'fe7e5ab6086a37067f029cbf80d55caf',
  './modules/Administration/QuickRepairAndRebuild.php' => '44b428d870c178243326bbf1d51cb6dd',
  './modules/Administration/UpgradeWizard.php' => 'b4324887e5715f94d06fdc2c0f91ec1f',
  './modules/Campaigns/WebToPersonCapture.php' => '27e24a36a16897c8a4fac043c8b85516',
  './modules/Campaigns/utils.php' => 'ec1404fdd8f9051b132b776b08472854',
  './modules/Contacts/ContactFormBase.php' => '093d97d4d6020217c3d41d38136f78bc',
  './modules/EmailTemplates/AttachFiles.php' => '89b77bcfe0184454af0af2b6888d3f44',
  './modules/Emails/include/ComposeView/ComposeView.tpl' => 'aee5736e992aecc485afbb5973d9e535',
  './modules/Employees/Employee.php' => '54eaa8f299aa57e555f316b80e71eaab',
  './modules/Leads/LeadFormBase.php' => 'ad656b689844598451098ad61f43ab87',
  './modules/Prospects/ProspectFormBase.php' => 'b48c69d3a1f89a43ac79fbd6febb0f1f',
  './modules/Studio/parsers/StudioParser.php' => '3df803d50e322cea269673d0f6c5470d',
  './modules/Surveys/Menu.php' => 'ecfbb08b99bb8e379eecd436b4922856',
  './modules/Users/User.php' => 'a75ad79197349bc156f9365ef3830b9c',
  './modules/Users/tpls/EditViewFooter.tpl' => '3468ee673ada79bdb8129aeca2a739b6',
  './suitecrm_version.php' => '1b607bcbe64dcc887e9f205ef6d22071',
  './themes/SuiteP/include/DetailView/DetailView.tpl' => 'ff62ab3dc8d5b6e91a4c1041c043592e',
);
        
            'svg'
    if (empty($module)) {
RedirectMatch 403 {$ignoreCase}/+(soap|cache|xtemplate|data|examples|include|log4php|metadata|modules|vendor)/+.*\.(php|tpl)
 
    
            $moduleDir = $_REQUEST['load_module_from_dir'];
            if (strpos($moduleDir, 'phar://') !== false) {
                die();
        
        
        
                
function get_message_scope_dom($campaign_id, $campaign_name, $db=null, $mod_strings=array())
    $query =  "SELECT prospect_list_id, prospect_lists.name ";
            
            
            
            
            
        
            $query .= " contacts.first_name LIKE '". $_POST[$prefix.'first_name'] . "%' AND contacts.last_name = '". $_POST[$prefix.'last_name'] ."'";
            $query .= " contacts.last_name = '". $_POST[$prefix.'last_name'] ."'";
            $query .= " AND  contacts.id != '". $_POST[$prefix.'record'] ."'";
            'module': '{$RETURN_MODULE}',
            'id': '{$RETURN_ID}'
            $query .= " (first_name='". $_POST[$prefix.'first_name'] . "' AND last_name = '". $_POST[$prefix.'last_name'] ."')";
            $query .= " last_name = '". $_POST[$prefix.'last_name'] ."'";
    
            $query = $baseQuery ."  (first_name like '". $_POST[$prefix.'first_name'] . "%' and last_name = '". $_POST[$prefix.'last_name'] ."')";
            $query = $baseQuery ."  last_name = '". $_POST[$prefix.'last_name'] ."'";
                $query = $baseQuery. "  email1='". $_POST[$prefix.'email1'] . "' or email2 = '". $_POST[$prefix.'email1'] ."'";
                $query .= "or email1='". $_POST[$prefix.'email1'] . "' or email2 = '". $_POST[$prefix.'email1'] ."'";
                $query = $baseQuery. "  email1='". $_POST[$prefix.'email2'] . "' or email2 = '". $_POST[$prefix.'email2'] ."'";
                $query .= "or email1='". $_POST[$prefix.'email2'] . "' or email2 = '". $_POST[$prefix.'email2'] ."'";
        
        
    
    
    
    
    
    
    
    
    
    
    
        $isUpdate = !empty($this->id) && !$this->new_with_id;
        // If the current user is not an admin, do not allow them to set the admin flag to true.
        if (!is_admin($current_user)) {
            $this->is_admin = 0;
        }

$suitecrm_version = '7.12.8';
$suitecrm_timestamp = '2022-11-17 12:00:00';

Fixed code:
        $allowedField = [];

        $fieldsToParse = $fields;
        if (empty($fields)) {
            $fieldsToParse =  array_keys($bean->field_defs);
        }

        foreach ($fieldsToParse ?? [] as $index => $field) {
            $isSensitive = isTrue($bean->field_defs[$field]['sensitive'] ?? false);
            $notApiVisible = isFalse($bean->field_defs[$field]['api-visible'] ?? true);

            if ($isSensitive || $notApiVisible){
                continue;
            }

            $allowedField[$index] = $field;
        }

        if ($allowedField !== null) {
            $attributes = array_intersect_key($attributes, array_flip($allowedField));
# SuiteCRM 7.12.9
    /**
     * Returns a SugarBean object by id.
     *
     * @static
     *
     * @param string $module
     * @param string $id
     * @param array $params
     *        A name/value array of parameters. Names: encode, deleted.
     *        If $params is boolean we revert to the old arguments (encode, deleted), and use $params as $encode.
     *        This will be changed to using only $params in later versions.
     * @param bool $deleted
     *        @see SugarBean::retrieve
     *
     * @return SugarBean|bool
     */
    public static function getReloadedBean($module, $id = null, $params = [], $deleted = true)
    {
        $params = self::convertParams($params);
        $encode = self::hasEncodeFlag($params);
        $deleted = self::hasDeletedFlag($params, $deleted);

        $beanClass = self::getBeanClass($module);

        if (!self::loadBeanFile($module)) {
            return false;
        }

        if (empty($id)) {
            return new $beanClass();
        }

        /* @var SugarBean $bean */
        $bean = new $beanClass();

        $result = $bean->retrieve($id, $encode, $deleted);

        if ($result === null) {
            return false;
        }

        return $bean;
    }


// created: 2023-01-25 17:00:00
  './Api/V8/JsonApi/Helper/AttributeObjectHelper.php' => '3336452c51560f9a40c107d0ace78165',
  './README.md' => '01f0f8317b743d7d0318251b7d65ac3a',
  './data/BeanFactory.php' => '3007bf65ebc77baa76c338ce6a068790',
  './include/EditView/EditView2.php' => '7ad84a44d52c2d5ae9a7e51d170a0efc',
  './include/InlineEditing/InlineEditing.php' => '981e9c7c35b0f2aefa192ebf46eb3c44',
  './include/MySugar/MySugar.php' => 'eaffdea2231df9ec4a457fb57d499ca4',
  './include/SugarTinyMCE.php' => '9be7450638b50efe57ccc33bfbb35eb0',
  './include/database/MysqliManager.php' => '19025de16a37f6bb8ffb1b8c4e0ab34d',
  './include/language/en_us.lang.php' => 'aad6885d43921e1228b124d4c8b6222c',
  './include/utils.php' => '6107d7454bb0720ac43e498aca5e9e74',
  './install/install_utils.php' => '4a8c2ea13fcd9c5902f13a8660fc6263',
  './lib/API/JsonApi/v1/Resource/SuiteBeanResource.php' => '896c39fb5fdd461dfb87c092d220143d',
  './lib/Search/ElasticSearch/ElasticSearchEngine.php' => '728d22251a99bf9606170fc1b48279ef',
  './lib/Search/ElasticSearch/ElasticSearchIndexer.php' => '3775a9343fb6e0323a35a592c5b94f87',
  './lib/Search/SearchQuery.php' => '523292cc81871ec25ac17a7ab96bc888',
  './lib/Search/UI/SearchResultsController.php' => '788728a30dc77334a9682e49ff9f0307',
  './lib/Utility/ArrayMapper.php' => '03c31accfe841c560daafe3d2d86c672',
  './modules/AOS_Products/AOS_Products.php' => '0f4f0237cd42e4cdece3d894a53ea9c1',
  './modules/Administration/QuickRepairAndRebuild.php' => '14a4e14865732a12f4cbda21add1c8cc',
  './modules/Administration/UpgradeWizard.php' => 'd114a8c831c909d1a4c48b0afac7b8a0',
  './modules/Campaigns/WebToPersonCapture.php' => 'c74104628b33fc9b2a31f2d46bf3809f',
  './modules/Campaigns/utils.php' => 'e923cf376d1da33da2230f332f9c1baa',
  './modules/Contacts/ContactFormBase.php' => '4aa3fc8f820d2877c4840ea445ac60c4',
  './modules/EmailTemplates/AttachFiles.php' => '38b7dbce86a3bb85986f1001ae22b765',
  './modules/Emails/include/ComposeView/ComposeView.tpl' => '02a1b64bfde0a319fcac90b1b9ebda13',
  './modules/Employees/Employee.php' => 'acf053a95024ad9d06d55457fc641d6f',
  './modules/Leads/LeadFormBase.php' => '2c1645940849161817ce36e836c6f028',
  './modules/Prospects/ProspectFormBase.php' => '0fd6256da04924ebd8968edb2723bc34',
  './modules/Studio/parsers/StudioParser.php' => 'dbd08a554f38f79d34a44e36c6477dc8',
  './modules/Surveys/Menu.php' => 'ba18e736af4a1d2a261fbf67d3f174b2',
  './modules/Users/User.php' => '5edd56612b566c0d8b6199f6e2973575',
  './modules/Users/tpls/EditViewFooter.tpl' => '09e287fec2fef683c1aedd71598e52e5',
  './suitecrm_version.php' => '4ef3194ff974804a8f3481681fcfc7ff',
  './themes/SuiteP/include/DetailView/DetailView.tpl' => 'ca729f1cf4e16c84566d69d5d471bdfb',
);
    $vardef = $bean->getFieldDefinition($fieldname);

    if (isTrue($vardef['sensitive'] ?? false) || isFalse($vardef['api-visible'] ?? true)){
        return false;
    }

            if (!empty($_POST['type_module']) && stripos($_POST['type_module'], 'phar://') !== false) {
                LoggerManager::getLogger()->security('MySugar:addDashlet unsecure type_module received: ' . $_POST['type_module']);
                throw new RuntimeException('Invalid type_module');
            }

            'php6',
            'php7',
            'php8',
            'php6',
            'php7',
            'php8',
            'svg',
            'bmp'
    if (empty($module) || !isAllowedModuleName($module)) {

/**
 * Check if is string is an allowed module name
 * @param string $value
 * @return bool
 */
function isAllowedModuleName(string $value): bool {
    if (empty($value)) {
        return false;
    }

    $result = preg_match("/^[\w\-\_\.]+$/", $value);

    if (!empty($result)) {
        return true;
    }

    return false;
}
RedirectMatch 403 {$ignoreCase}/+(soap|cache|xtemplate|data|examples|include|log4php|metadata|modules|vendor)/+.*\.(php|tpl|phar)


            $isSensitive = isTrue($definition['sensitive'] ?? false);
            $notApiVisible = isFalse($definition['api-visible'] ?? true);

            if ($isSensitive || $notApiVisible){
                continue;
            }

        $imageFileName = $_FILES['uploadimage']['name'] ?? '';
        if (!has_valid_image_extension('AOS_Products Uploaded image file: ' . $imageFileName , $imageFileName)) {
            LoggerManager::getLogger()->fatal("AOS_Products save - Invalid image file ext : '$imageFileName'.");
            throw new RuntimeException('Invalid request');
        }

            $moduleDir = $_REQUEST['load_module_from_dir'] ?? '';
            if (stripos($moduleDir, 'phar://') !== false) {
                LoggerManager::getLogger()->fatal("UpgradeWizard - invalid load_module_from_dir: " . $moduleDir);
                throw new RuntimeException('Invalid request');
            }

            if (strtolower(pathinfo(urldecode($_REQUEST['upgrade_zip_escaped'] ?? ''), PATHINFO_EXTENSION)) !== 'zip'){
                LoggerManager::getLogger()->fatal("UpgradeWizard - invalid upgrade_zip_escaped: " . $_REQUEST['upgrade_zip_escaped'] ?? '');
                throw new RuntimeException("Invalid request");
if (!isValidWebToPersonModule($moduleDir)) {
    LoggerManager::getLogger()->fatal('Trying to run WepToPersonCapture for invalid module: ' . $moduleDir);
    throw new RuntimeException('Not a valid module');
}





function get_message_scope_dom($campaign_id, $campaign_name, $db = null, $mod_strings = array())
    $query = "SELECT prospect_list_id, prospect_lists.name ";







/**
 * Get valid web to person modules
 * @return array
 */
function getValidWebToPersonModules(): array
{
    $superclass = 'Person';
    $modules = [];
    foreach ($GLOBALS['moduleList'] as $mod) {
        $item = BeanFactory::getBean($mod);
        if ($item && is_subclass_of($item, $superclass)) {
            $modules[] = $item->module_name;
        }
    }

    return $modules;
}

/**
 * Check if it is a valid WebToPerson module
 * @param string $module
 * @return bool
 */
function isValidWebToPersonModule(string $module): bool
{
    $validModules = getValidWebToPersonModules();

    return in_array($module, $validModules, true);
}
        $dbManager = DBManagerFactory::getInstance();

            $firstName = $dbManager->quote($_POST[$prefix.'first_name' ?? '']);
            $lastName = $dbManager->quote($_POST[$prefix.'last_name' ?? '']);
            $query .= " contacts.first_name LIKE '". $firstName . "%' AND contacts.last_name = '". $lastName ."'";
            $lastName = $dbManager->quote($_POST[$prefix.'last_name' ?? '']);
            $query .= " contacts.last_name = '". $lastName ."'";
            $record = $dbManager->quote($_POST[$prefix.'record' ?? '']);
            $query .= " AND  contacts.id != '". $record ."'";
        $fileName = $_FILES[$k]['name'] ?? '';

        if (!has_valid_image_extension('Attach Files Uploaded file: ' . $fileName , $fileName)) {
            LoggerManager::getLogger()->fatal("EmailTemplates AttachFiles - Invalid file ext : '$fileName'.");
            throw new RuntimeException('Invalid request');
        }

            'module': '{$RETURN_MODULE|escape:'javascript'}',
            'id': '{$RETURN_ID|escape:'javascript'}'
        // If the current user is not an admin, reset the admin flag to the original value.
        $this->setIsAdmin();


    /**
     * Reset is_admin if current user is not an admin user
     * @return void
     */
    protected function setIsAdmin(): void
    {
        global $current_user;

        if (!isset($this->is_admin)) {
            return;
        }

        $originalIsAdminValue = $this->is_admin ?? false;
        if ($this->isUpdate() && isset($this->fetched_row['is_admin'])) {
            $originalIsAdminValue = isTrue($this->fetched_row['is_admin'] ?? false);
        }

        $currentUserReloaded = BeanFactory::getReloadedBean('Users', $current_user->id);
        if (!is_admin($currentUserReloaded)) {
            $this->is_admin = $originalIsAdminValue;
        }

    }

    /**
     * @return bool
     */
    protected function isUpdate(): bool
    {
        return !empty($this->id) && !$this->new_with_id;
    }
        $dbManager = DBManagerFactory::getInstance();
            $firstName = $dbManager->quote($_POST[$prefix.'first_name' ?? '']);
            $lastName = $dbManager->quote($_POST[$prefix.'last_name' ?? '']);
            $query .= " (first_name='". $firstName . "' AND last_name = '". $lastName ."')";
            $lastName = $dbManager->quote($_POST[$prefix.'last_name' ?? '']);
            $query .= " last_name = '". $lastName ."'";


        $dbManager = DBManagerFactory::getInstance();

            $firstName = $dbManager->quote($_POST[$prefix.'first_name' ?? '']);
            $lastName = $dbManager->quote($_POST[$prefix.'last_name' ?? '']);
            $query = $baseQuery ."  (first_name like '". $firstName . "%' and last_name = '". $lastName ."')";
            $lastName = $dbManager->quote($_POST[$prefix.'last_name' ?? '']);
            $query = $baseQuery ."  last_name = '". $lastName ."'";
            $email1 = $dbManager->quote($_POST[$prefix.'email1' ?? '']);
                $query = $baseQuery. "  email1='". $email1 . "' or email2 = '". $email1 ."'";
                $query .= "or email1='". $email1 . "' or email2 = '". $email1 ."'";
            $email2 = $dbManager->quote($_POST[$prefix.'email2' ?? '']);
                $query = $baseQuery. "  email1='". $email2 . "' or email2 = '". $email2 ."'";
                $query .= "or email1='". $email2 . "' or email2 = '". $email2 ."'";













        $isUpdate = $this->isUpdate();

        // If the current user is not an admin, reset the admin flag to the original value.
        $this->setIsAdmin();


    /**
     * Reset is_admin if current user is not an admin user
     * @return void
     */
    protected function setIsAdmin(): void
    {
        global $current_user;
        if (!isset($this->is_admin)) {
            return;
        }


        $originalIsAdminValue = $this->is_admin ?? false;
        if ($this->isUpdate() && isset($this->fetched_row['is_admin'])) {
            $originalIsAdminValue = isTrue($this->fetched_row['is_admin'] ?? false);
        }

        $currentUserReloaded = BeanFactory::getReloadedBean('Users', $current_user->id);
        if (!is_admin($currentUserReloaded)) {
            $this->is_admin = $originalIsAdminValue;
        }

    }

    /**
     * @return bool
     */
    protected function isUpdate(): bool
    {
        return !empty($this->id) && !$this->new_with_id;
    }
$suitecrm_version = '7.12.9';
$suitecrm_timestamp = '2023-01-25 12:00:00';