'pre_execute', 'install_mkdirs'=>'mkdir', 'install_copy'=>'copy', 'install_images'=>'image_dir', 'install_menus'=>'menu', 'install_userpage'=>'user_page', 'install_dashlets'=>'dashlets', 'install_administration'=>'administration', 'install_connectors'=>'connectors', 'install_vardefs'=>'vardefs', 'install_layoutdefs'=>'layoutdefs', 'install_layoutfields'=>'layoutfields', 'install_relationships'=>'relationships', 'install_languages'=>'language', 'install_logichooks'=>'logic_hooks', 'post_execute'=>'post_execute', ); /** * config settings * @var array */ private $config = array(); private $config_hash; private $blackListExempt = array(); private $classBlackListExempt = array(); // Bug 56717 - adding hbs extension to the whitelist - rgonzalez private $validExt = array('png', 'gif', 'jpg', 'css', 'js', 'php', 'txt', 'html', 'htm', 'tpl', 'pdf', 'md5', 'xml', 'hbs'); private $classBlackList = array( // Class names specified here must be in lowercase as the implementation // of the tokenizer converts all tokens to lowercase. 'reflection', 'reflectionclass', 'reflectionzendextension', 'reflectionextension', 'reflectionfunction', 'reflectionfunctionabstract', 'reflectionmethod', 'reflectionobject', 'reflectionparameter', 'reflectionproperty', 'reflector', 'reflectionexception', 'lua', 'ziparchive', 'splfileinfo', 'splfileobject', 'pclzip', ); private $blackList = array( 'popen', 'proc_open', 'escapeshellarg', 'escapeshellcmd', 'proc_close', 'proc_get_status', 'proc_nice', 'passthru', 'clearstatcache', 'disk_free_space', 'disk_total_space', 'diskfreespace', 'dir', 'fclose', 'feof', 'fflush', 'fgetc', 'fgetcsv', 'fgets', 'fgetss', 'file_exists', 'file_get_contents', 'filesize', 'filetype', 'flock', 'fnmatch', 'fpassthru', 'fputcsv', 'fputs', 'fread', 'fscanf', 'fseek', 'fstat', 'ftell', 'ftruncate', 'fwrite', 'glob', 'is_dir', 'is_file', 'is_link', 'is_readable', 'is_uploaded_file', 'opendir', 'parse_ini_string', 'pathinfo', 'pclose', 'readfile', 'readlink', 'realpath_cache_get', 'realpath_cache_size', 'realpath', 'rewind', 'readdir', 'set_file_buffer', 'tmpfile', 'umask', 'ini_set', 'set_time_limit', 'eval', 'exec', 'system', 'shell_exec', 'passthru', 'chgrp', 'chmod', 'chwown', 'file_put_contents', 'file', 'fileatime', 'filectime', 'filegroup', 'fileinode', 'filemtime', 'fileowner', 'fileperms', 'fopen', 'is_executable', 'is_writable', 'is_writeable', 'lchgrp', 'lchown', 'linkinfo', 'lstat', 'mkdir', 'mkdir_recursive', 'parse_ini_file', 'rmdir', 'rmdir_recursive', 'stat', 'tempnam', 'touch', 'unlink', 'getimagesize', 'call_user_func', 'call_user_func_array', 'create_function', //mutliple files per function call 'copy', 'copy_recursive', 'link', 'rename', 'symlink', 'move_uploaded_file', 'chdir', 'chroot', 'create_cache_directory', 'mk_temp_dir', 'write_array_to_file', 'write_encoded_file', 'create_custom_directory', 'sugar_rename', 'sugar_chown', 'sugar_fopen', 'sugar_mkdir', 'sugar_file_put_contents', 'sugar_chgrp', 'sugar_chmod', 'sugar_touch', // Functions that have callbacks can circumvent our security measures. // List retrieved through PHP's XML documentation, and running the // following script in the reference directory: // grep -R callable . | grep -v \.svn | grep methodparam | cut -d: -f1 | sort -u | cut -d"." -f2 | sed 's/\-/\_/g' | cut -d"/" -f4 // AMQPQueue 'consume', // PHP internal - arrays 'array_diff_uassoc', 'array_diff_ukey', 'array_filter', 'array_intersect_uassoc', 'array_intersect_ukey', 'array_map', 'array_reduce', 'array_udiff_assoc', 'array_udiff_uassoc', 'array_udiff', 'array_uintersect_assoc', 'array_uintersect_uassoc', 'array_uintersect', 'array_walk_recursive', 'array_walk', 'uasort', 'uksort', 'usort', // EIO functions that accept callbacks. 'eio_busy', 'eio_chmod', 'eio_chown', 'eio_close', 'eio_custom', 'eio_dup2', 'eio_fallocate', 'eio_fchmod', 'eio_fchown', 'eio_fdatasync', 'eio_fstat', 'eio_fstatvfs', 'eio_fsync', 'eio_ftruncate', 'eio_futime', 'eio_grp', 'eio_link', 'eio_lstat', 'eio_mkdir', 'eio_mknod', 'eio_nop', 'eio_open', 'eio_read', 'eio_readahead', 'eio_readdir', 'eio_readlink', 'eio_realpath', 'eio_rename', 'eio_rmdir', 'eio_sendfile', 'eio_stat', 'eio_statvfs', 'eio_symlink', 'eio_sync_file_range', 'eio_sync', 'eio_syncfs', 'eio_truncate', 'eio_unlink', 'eio_utime', 'eio_write', // PHP internal - error functions 'set_error_handler', 'set_exception_handler', // Forms Data Format functions 'fdf_enum_values', // PHP internal - function handling 'call_user_func_array', 'call_user_func', 'forward_static_call_array', 'forward_static_call', 'register_shutdown_function', 'register_tick_function', // Gearman 'setclientcallback', 'setcompletecallback', 'setdatacallback', 'setexceptioncallback', 'setfailcallback', 'setstatuscallback', 'setwarningcallback', 'setworkloadcallback', 'addfunction', // Firebird/InterBase 'ibase_set_event_handler', // LDAP 'ldap_set_rebind_proc', // LibXML 'libxml_set_external_entity_loader', // Mailparse functions 'mailparse_msg_extract_part_file', 'mailparse_msg_extract_part', 'mailparse_msg_extract_whole_part_file', // Memcache(d) functions 'addserver', 'setserverparams', 'get', 'getbykey', 'getdelayed', 'getdelayedbykey', // MySQLi 'set_local_infile_handler', // PHP internal - network functions 'header_register_callback', // Newt 'newt_entry_set_filter', 'newt_set_suspend_callback', // OAuth 'consumerhandler', 'timestampnoncehandler', 'tokenhandler', // PHP internal - output control 'ob_start', // PHP internal - PCNTL 'pcntl_signal', // PHP internal - PCRE 'preg_replace_callback', // SQLite 'sqlitecreateaggregate', 'sqlitecreatefunction', 'sqlite_create_aggregate', 'sqlite_create_function', // RarArchive 'open', // Readline 'readline_callback_handler_install', 'readline_completion_function', // PHP internal - session handling 'session_set_save_handler', // PHP internal - SPL 'construct', 'iterator_apply', 'spl_autoload_register', // Sybase 'sybase_set_message_handler', // PHP internal - variable handling 'is_callable', // XML Parser 'xml_set_character_data_handler', 'xml_set_default_handler', 'xml_set_element_handler', 'xml_set_end_namespace_decl_handler', 'xml_set_external_entity_ref_handler', 'xml_set_notation_decl_handler', 'xml_set_processing_instruction_handler', 'xml_set_start_namespace_decl_handler', 'xml_set_unparsed_entity_decl_handler', // unzip 'unzip', 'unzip_file', ); private $methodsBlackList = array('setlevel', 'put' => array('sugarautoloader'), 'unlink' => array('sugarautoloader')); public function printToWiki(){ echo "'''Default Extensions'''
"; foreach($this->validExt as $b){ echo '#' . $b . '
'; } echo "'''Default Black Listed Functions'''
"; foreach($this->blackList as $b){ echo '#' . $b . '
'; } } public function __construct() { $params = array( 'blackListExempt' => 'MODULE_INSTALLER_PACKAGE_SCAN_BLACK_LIST_EXEMPT', 'blackList' => 'MODULE_INSTALLER_PACKAGE_SCAN_BLACK_LIST', 'classBlackListExempt' => 'MODULE_INSTALLER_PACKAGE_SCAN_CLASS_BLACK_LIST_EXEMPT', 'classBlackList' => 'MODULE_INSTALLER_PACKAGE_SCAN_CLASS_BLACK_LIST', 'validExt' => 'MODULE_INSTALLER_PACKAGE_SCAN_VALID_EXT', 'methodsBlackList' => 'MODULE_INSTALLER_PACKAGE_SCAN_METHOD_LIST', ); $disableConfigOverride = defined('MODULE_INSTALLER_DISABLE_CONFIG_OVERRIDE') && MODULE_INSTALLER_DISABLE_CONFIG_OVERRIDE; $disableDefineOverride = defined('MODULE_INSTALLER_DISABLE_DEFINE_OVERRIDE') && MODULE_INSTALLER_DISABLE_DEFINE_OVERRIDE; if (!$disableConfigOverride && !empty($GLOBALS['sugar_config']['moduleInstaller'])) { $this->config = $GLOBALS['sugar_config']['moduleInstaller']; } foreach ($params as $param => $constName) { if (!$disableConfigOverride && isset($this->config[$param]) && is_array($this->config[$param])) { $this->{$param} = array_merge($this->{$param}, $this->config[$param]); } if (!$disableDefineOverride && defined($constName)) { $value = constant($constName); $value = explode(',', $value); $value = array_map('trim', $value); $value = array_filter($value, 'strlen'); $this->{$param} = array_merge($this->{$param}, $value); } } } private $issues = array(); private $pathToModule = ''; /** *returns a list of issues */ public function getIssues(){ return $this->issues; } /** *returns true or false if any issues were found */ public function hasIssues(){ return !empty($this->issues); } /** *Ensures that a file has a valid extension */ public function isValidExtension($file) { $file = strtolower($file); $pi = pathinfo($file); //make sure they don't override the files.md5 if(empty($pi['extension']) || $pi['basename'] == 'files.md5') { return false; } return in_array($pi['extension'], $this->validExt); } public function isConfigFile($file) { $real = realpath($file); if($real == realpath("config.php")) { return true; } if(file_exists("config_override.php") && $real == realpath("config_override.php")) { return true; } return false; } /** *Scans a directory and calls on scan file for each file **/ public function scanDir($path){ static $startPath = ''; if(empty($startPath))$startPath = $path; if(!is_dir($path))return false; $d = dir($path); while($e = $d->read()){ $next = $path . '/' . $e; if(is_dir($next)){ if(substr($e, 0, 1) == '.')continue; $this->scanDir($next); }else{ $issues = $this->scanFile($next); } } return true; } /** * Check if the file contents looks like PHP * @param string $contents File contents * @return boolean */ public function isPHPFile($contents) { if(stripos($contents, 'isValidExtension($file)){ $issues[] = translate('ML_INVALID_EXT'); $this->issues['file'][$file] = $issues; return $issues; } if($this->isConfigFile($file)){ $issues[] = translate('ML_OVERRIDE_CORE_FILES'); $this->issues['file'][$file] = $issues; return $issues; } $contents = file_get_contents($file); if(!$this->isPHPFile($contents)) return $issues; $tokens = @token_get_all($contents); $checkFunction = false; $possibleIssue = ''; $lastToken = false; foreach($tokens as $index=>$token){ if(is_string($token[0])){ switch($token[0]){ case '`': $issues['backtick'] = translate('ML_INVALID_FUNCTION') . " '`'"; case '(': if($checkFunction)$issues[] = $possibleIssue; break; } $checkFunction = false; $possibleIssue = ''; }else{ $token['_msi'] = token_name($token[0]); switch($token[0]){ case T_WHITESPACE: continue; case T_EVAL: if(in_array('eval', $this->blackList) && !in_array('eval', $this->blackListExempt)) $issues[]= translate('ML_INVALID_FUNCTION') . ' eval()'; break; case T_STRING: $token[1] = strtolower($token[1]); if($lastToken !== false && $lastToken[0] == T_NEW) { if(!in_array($token[1], $this->classBlackList))break; if(in_array($token[1], $this->classBlackListExempt))break; } elseif ($token[0] == T_DOUBLE_COLON) { if(!in_array($lastToken[1], $this->classBlackList))break; if(in_array($lastToken[1], $this->classBlackListExempt))break; } else { //if nothing else fit, lets check the last token to see if this is a possible method call if ($lastToken !== false && ($lastToken[0] == T_OBJECT_OPERATOR || $lastToken[0] == T_DOUBLE_COLON)) { // check static blacklist for methods if(!empty($this->methodsBlackList[$token[1]])) { if($this->methodsBlackList[$token[1]] == '*') { $issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1]. '()'; break; } else { if($lastToken[0] == T_DOUBLE_COLON && $index > 2 && $tokens[$index-2][0] == T_STRING) { $classname = strtolower($tokens[$index-2][1]); if(in_array($classname, $this->methodsBlackList[$token[1]])) { $issues[]= translate('ML_INVALID_METHOD') . ' ' .$classname . '::' . $token[1]. '()'; break; } } } } //this is a method call, check the black list if(in_array($token[1], $this->methodsBlackList)){ $issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1]. '()'; } break; } if(!in_array($token[1], $this->blackList))break; if(in_array($token[1], $this->blackListExempt))break; } case T_VARIABLE: $checkFunction = true; $possibleIssue = translate('ML_INVALID_FUNCTION') . ' ' . $token[1] . '()'; break; default: $checkFunction = false; $possibleIssue = ''; } if ($token[0] != T_WHITESPACE) { $lastToken = $token; } } } if(!empty($issues)){ $this->issues['file'][$file] = $issues; } return $issues; } /* * checks files.md5 file to see if the file is from sugar * ONLY WORKS ON FILES */ public function sugarFileExists($path){ static $md5 = array(); if(empty($md5) && file_exists('files.md5')) { include('files.md5'); $md5 = $md5_string; } if(isset($md5['./' . $path]))return true; } /** *This function will scan the Manifest for disabled actions specified in $GLOBALS['sugar_config']['moduleInstaller']['disableActions'] *if $GLOBALS['sugar_config']['moduleInstaller']['disableRestrictedCopy'] is set to false or not set it will call on scanCopy to ensure that it is not overriding files */ public function scanManifest($manifestPath){ $issues = array(); if(!file_exists($manifestPath)){ $this->issues['manifest'][$manifestPath] = translate('ML_NO_MANIFEST'); return $issues; } $fileIssues = $this->scanFile($manifestPath); //if the manifest contains malicious code do not open it if(!empty($fileIssues)){ return $fileIssues; } $this->lockConfig(); list($manifest, $installdefs) = MSLoadManifest($manifestPath); $fileIssues = $this->checkConfig($manifestPath); if(!empty($fileIssues)){ return $fileIssues; } //scan for disabled actions if(isset($this->config['disableActions'])){ foreach($this->config['disableActions'] as $action){ if(isset($installdefs[$this->manifestMap[$action]])){ $issues[] = translate('ML_INVALID_ACTION_IN_MANIFEST') . $this->manifestMap[$action]; } } } //now lets scan for files that will override our files if(empty($this->config['disableRestrictedCopy']) && isset($installdefs['copy'])){ foreach($installdefs['copy'] as $copy){ $from = str_replace('', $this->pathToModule, $copy['from']); $to = $copy['to']; if(substr_count($from, '..')){ $this->issues['copy'][$from] = translate('ML_PATH_MAY_NOT_CONTAIN').' ".." -' . $from; } if(substr_count($to, '..')){ $this->issues['copy'][$to] = translate('ML_PATH_MAY_NOT_CONTAIN'). ' ".." -' . $to; } while(substr_count($from, '//')){ $from = str_replace('//', '/', $from); } while(substr_count($to, '//')){ $to = str_replace('//', '/', $to); } $this->scanCopy($from, $to); } } if(!empty($issues)){ $this->issues['manifest'][$manifestPath] = $issues; } } /** * Takes in where the file will is specified to be copied from and to * and ensures that there is no official sugar file there. If the file exists it will check * against the MD5 file list to see if Sugar Created the file * */ function scanCopy($from, $to){ //if the file doesn't exist for the $to then it is not overriding anything if(!file_exists($to))return; //if $to is a dir and $from is a file then make $to a full file path as well if(is_dir($to) && is_file($from)){ if(substr($to,-1) === '/'){ $to = substr($to, 0 , strlen($to) - 1); } $to .= '/'. basename($from); } //if the $to is a file and it is found in sugarFileExists then don't allow overriding it if(is_file($to) && $this->sugarFileExists($to)){ $this->issues['copy'][$from] = translate('ML_OVERRIDE_CORE_FILES') . '(' . $to . ')'; } if(is_dir($from)){ $d = dir($from); while($e = $d->read()){ if($e == '.' || $e == '..')continue; $this->scanCopy($from .'/'. $e, $to .'/' . $e); } } } /** *Main external function that takes in a path to a package and then scans *that package's manifest for disabled actions and then it scans the PHP files *for restricted function calls * */ public function scanPackage($path){ $this->pathToModule = $path; $this->scanManifest($path . '/manifest.php'); if(empty($this->config['disableFileScan'])){ $this->scanDir($path); } } /** *This function will take all issues of the current instance and print them to the screen **/ public function displayIssues($package='Package'){ echo '

'.str_replace('{PACKAGE}' , $package ,translate('ML_PACKAGE_SCANNING')). '


' . translate('ML_INSTALLATION_FAILED') . '


' .str_replace('{PACKAGE}' , $package ,translate('ML_PACKAGE_NOT_CONFIRM')). '


' . translate('ML_SUGAR_LOADING_POLICY') . ' ' . translate('ML_SUGAR_KB') . '.'. '
' . translate('ML_AVAIL_RESTRICTION'). ' ' . translate('ML_SUGAR_DZ') . '.

'; foreach($this->issues as $type=>$issues){ echo '

'. ucfirst($type) .' ' . translate('ML_ISSUES') . '

'; echo '
'; foreach($issues as $file=>$issue){ $file = str_replace($this->pathToModule . '/', '', $file); echo '
' . $file . '
'; if(is_array($issue)){ foreach($issue as $i){ echo "$i
"; } }else{ echo "$issue
"; } echo "
"; } echo '
'; } echo "
"; } /** * Lock config settings */ public function lockConfig() { if(empty($this->config_hash)) { $this->config_hash = md5(serialize($GLOBALS['sugar_config'])); } } /** * Check if config was modified. Return * @param string $file * @return array Errors if something wrong, false if no problems */ public function checkConfig($file) { $config_hash_after = md5(serialize($GLOBALS['sugar_config'])); if($config_hash_after != $this->config_hash) { $this->issues['file'][$file] = array(translate('ML_CONFIG_OVERRIDE')); return $this->issues; } return false; } } /** * Load manifest file * Outside of the class to isolate the context * @param string $manifest_file * @return array */ function MSLoadManifest($manifest_file) { include( $manifest_file ); return array($manifest, $installdefs); } ?>