%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/security/classes/general/tests/ |
Current File : /home/bitrix/www/bitrix/modules/security/classes/general/tests/environment.php |
<? /** * Bitrix Framework * @package bitrix * @subpackage security * @copyright 2001-2013 Bitrix */ /** * Class CSecurityEnvironmentTest * @since 12.5.0 */ class CSecurityEnvironmentTest extends CSecurityBaseTest { const MIN_UID = 10; const MIN_GID = 10; protected $internalName = "EnvironmentTest"; protected $tests = array( "sessionDir" => array( "method" => "checkPhpSessionDir" ), "collectivePhpSession" => array( "method" => "checkCollectivePhpSession" ), "uploadScriptExecution" => array( "method" => "checkUploadScriptExecution" ), "uploadNegotiationEnabled" => array( "method" => "checkUploadNegotiationEnabled" ), "privilegedPhpUserOrGroup" => array( "method" => "checkPhpUserAndGroup" ), "bitrixTempPath" => array( "method" => "checkBitrixTempPath" ) ); //TODO: check custom php/py/perl/etc handlers in .htaccess files public function __construct() { IncludeModuleLangFile(__FILE__); } /** * Check if any server-side script executed in /upload dir and push those information to detail error * @return bool */ protected function checkUploadScriptExecution() { $baseMessageKey = "SECURITY_SITE_CHECKER_UPLOAD_EXECUTABLE"; $isHtaccessOverrided = false; // ToDo: fix and enable later // if(self::isHtaccessOverrided()) // { // $isHtaccessOverrided = true; // $this->addUnformattedDetailError("SECURITY_SITE_CHECKER_UPLOAD_HTACCESS", CSecurityCriticalLevel::LOW); // } $isPhpExecutable = false; $uniqueString = randString(20); if(self::isScriptExecutable("test.php", "<?php echo '{$uniqueString}'; ?>", $uniqueString)) { $isPhpExecutable = true; $this->addUnformattedDetailError($baseMessageKey."_PHP", CSecurityCriticalLevel::LOW); } $isPhpDoubleExtensionExecutable = false; if(!$isPhpExecutable && self::isScriptExecutable("test.php.any", "<?php echo '{$uniqueString}'; ?>", $uniqueString)) { $isPhpDoubleExtensionExecutable = true; $this->addUnformattedDetailError($baseMessageKey."_PHP_DOUBLE", CSecurityCriticalLevel::LOW); } $isPythonCgiExecutable = false; if(self::isScriptExecutable("test.py", "print 'Content-type:text/html\\r\\n\\r\\n{$uniqueString}'", $uniqueString)) { $isPythonCgiExecutable = true; $this->addUnformattedDetailError($baseMessageKey."_PY", CSecurityCriticalLevel::LOW); } if ($isPhpExecutable || $isPhpDoubleExtensionExecutable || $isHtaccessOverrided || $isPythonCgiExecutable) return self::STATUS_FAILED; else return self::STATUS_PASSED; } /** * Check if Apache Content Negotiation enabled in /upload dir and push those information to detail error * @return bool */ protected function checkUploadNegotiationEnabled() { $testingText = "test"; $testFileContent = " Content-language: ru Content-type: text/html; Body:----------ru-- ".$testingText." ----------ru-- "; if(self::isScriptExecutable("test.var.jpg", $testFileContent, $testingText)) { $this->addUnformattedDetailError("SECURITY_SITE_CHECKER_UPLOAD_NEGOTIATION", CSecurityCriticalLevel::MIDDLE); return self::STATUS_FAILED; } return self::STATUS_PASSED; } /** * Check apache AllowOverride * @return bool */ protected function isHtaccessOverrided() { $uploadDir = self::getUploadDir(); $uploadPathTestFile = $uploadDir.'test/test.php'; $uploadPathHtaccessFile = $uploadDir.'test/.htaccess'; $uploadPathTestUri = $uploadDir.'test/test_notexist.php'; if(!CheckDirPath($_SERVER['DOCUMENT_ROOT'].$uploadPathTestFile)) return false; $testingText = "testing text here..."; $htaccessText = <<<HTACCESS ErrorDocument 404 ${uploadPathTestFile} <IfModule mod_rewrite.c> RewriteEngine Off </IfModule> HTACCESS; $result = false; if(file_put_contents($_SERVER['DOCUMENT_ROOT'].$uploadPathTestFile, $testingText)) { if(file_put_contents($_SERVER['DOCUMENT_ROOT'].$uploadPathHtaccessFile, $htaccessText)) { $response = self::doRequestToLocalhost($uploadPathTestUri); if($response && $response == $testingText) { $result = true; } unlink($_SERVER['DOCUMENT_ROOT'].$uploadPathHtaccessFile); } unlink($_SERVER['DOCUMENT_ROOT'].$uploadPathTestFile); } return $result; } /** * Check minimal UID and GID * * @param int $minUid * @param int $minGid * @return bool */ protected function checkPhpUserAndGroup($minUid = self::MIN_UID, $minGid = self::MIN_GID) { if(self::isRunOnWin()) return self::STATUS_PASSED; $uid = self::getCurrentUID(); $uidCheckFailed = false; if($uid !== null && $uid < $minUid) $uidCheckFailed = true; $gid = self::getCurrentGID(); $gidCheckFailed = false; if($gid !== null && $gid < $minGid) $gidCheckFailed = true; if ($uidCheckFailed || $gidCheckFailed) { $this->addUnformattedDetailError( 'SECURITY_SITE_CHECKER_PHP_PRIVILEGED_USER', ($uid == 0 || $gid == 0 ? CSecurityCriticalLevel::HIGHT: CSecurityCriticalLevel::MIDDLE), getMessage('SECURITY_SITE_CHECKER_PHP_PRIVILEGED_USER_ADDITIONAL', array( '#UID#' => static::formatUID($uid), '#GID#' => static::formatGID($gid) )) ); return self::STATUS_FAILED; } return self::STATUS_PASSED; } /** * Return upload dir path for test usage * @return string */ protected static function getUploadDir() { return "/".COption::GetOptionString("main", "upload_dir", "upload")."/tmp/"; } /** * Return current domain name (in puny code for cyrillic domain) * @return string */ protected static function getCurrentHost() { $host = $_SERVER['HTTP_HOST'] ? $_SERVER['HTTP_HOST'] : 'localhost'; $errors = array(); return CBXPunycode::ToASCII($host, $errors); } /** * Return current site url, e.g. http://localhost:8990 * @return string */ protected static function getCurrentSiteUrl() { $url = $_SERVER['HTTPS'] == 'on' ? 'https://' : 'http://'; $url .= self::getCurrentHost(); $url .= $_SERVER['SERVER_PORT'] ? ':'.$_SERVER['SERVER_PORT'] : ''; return $url; } /** * Make request to current site and return result * @param string $pPath - url path, e.g. /upload/tmp/test.php * @return bool|string */ protected static function doRequestToLocalhost($pPath) { $url = self::getCurrentSiteUrl(); $url .= $pPath; $url .= "?".mt_rand(); //Prevent web-server cache return @CHTTP::sGet($url); } /** * @param string $pFileName - testing file name. e.g. test.php * @param string $pText - script entry * @param string $pSearch - text for searching after script execute * @return bool */ protected function isScriptExecutable($pFileName, $pText, $pSearch) { $uploadPath = self::getUploadDir().$pFileName; if(!CheckDirPath($_SERVER['DOCUMENT_ROOT'].$uploadPath)) return false; $result = false; if(file_put_contents($_SERVER['DOCUMENT_ROOT'].$uploadPath, $pText)) { $response = self::doRequestToLocalhost($uploadPath); if($response) { if($response != $pText && strpos($response, $pSearch) !== false) { $result = true; } } unlink($_SERVER['DOCUMENT_ROOT'].$uploadPath); } return $result; } /** * Return php tmp dir from environment * @return string */ protected static function getTmpDirFromEnv() { if ($_ENV["TMP"]) { return realpath($_ENV["TMP"]); } elseif ($_ENV["TMPDIR"]) { return realpath($_ENV["TMPDIR"]); } elseif ($_ENV["TEMP"]) { return realpath($_ENV["TEMP"]); } else { return ""; } } /** * Return php session or upload tmp dir * @param string $pPhpSettingKey * @return string */ protected static function getTmpDir($pPhpSettingKey = "upload_tmp_dir") { $result = ini_get($pPhpSettingKey); if(!$result) { if (function_exists("sys_get_temp_dir")) { $result = sys_get_temp_dir(); } else { $result = self::getTmpDirFromEnv(); } } return preg_replace('#[\\\/]+#', '/', $result); } /** * Return session unique ID * @return string */ protected static function getSessionUniqID() { return bitrix_sess_sign(); } /** * Check session files collective usage, e.g. several owners in the same session directory * @return bool */ protected function checkCollectivePhpSession() { if(self::isRunOnWin()) return self::STATUS_PASSED; if(COption::GetOptionString("security", "session") == "Y") return self::STATUS_PASSED; if(ini_get("session.save_handler") != "files") return self::STATUS_PASSED; $tmpDir = self::getTmpDir("session.save_path"); if(!$tmpDir) return self::STATUS_PASSED; $additionalInfo = ""; $isFailed = false; $currentUID = self::getCurrentUID(); $sessionSign = self::getSessionUniqID(); foreach (glob($tmpDir."/sess_*", GLOB_NOSORT) as $fileName) { if($currentUID !== null) { $fileOwner = fileowner($fileName); if($currentUID != $fileOwner) { $additionalInfo = getMessage("SECURITY_SITE_CHECKER_COLLECTIVE_SESSION_ADDITIONAL_OWNER", array( "#FILE#" => $fileName, "#FILE_ONWER#" => $fileOwner, "#CURRENT_OWNER#" => $currentUID, )); $isFailed = true; break; } } if(is_readable($fileName)) { $fileContent = file_get_contents($fileName); if (strpos($fileContent, $sessionSign) === false) { $additionalInfo = getMessage("SECURITY_SITE_CHECKER_COLLECTIVE_SESSION_ADDITIONAL_SIGN", array( "#FILE#" => $fileName, "#FILE_CONTENT#" => htmlspecialcharsbx(substr($fileContent, 0, 1024)), "#SIGN#" => $sessionSign )); $isFailed = true; break; } } } if($isFailed) { $this->addUnformattedDetailError( "SECURITY_SITE_CHECKER_COLLECTIVE_SESSION", CSecurityCriticalLevel::HIGHT, $additionalInfo ); return self::STATUS_FAILED; } return self::STATUS_PASSED; } /** * Check php session save dir for world accessible * @return bool */ protected function checkPhpSessionDir() { if (self::isRunOnWin()) return self::STATUS_PASSED; if (COption::GetOptionString("security", "session") == "Y") return self::STATUS_PASSED; if (ini_get("session.save_handler") != "files") return self::STATUS_PASSED; $tmpDir = self::getTmpDir("session.save_path"); if (!$tmpDir) return self::STATUS_PASSED; $dir = $tmpDir; while ($dir && $dir != '/') { $perms = static::getFilePerm($dir); if (($perms & 0x0001) === 0) return self::STATUS_PASSED; $dir = dirname($dir); } $this->addUnformattedDetailError( "SECURITY_SITE_CHECKER_SESSION_DIR", CSecurityCriticalLevel::HIGHT, getMessage("SECURITY_SITE_CHECKER_SESSION_DIR_ADDITIONAL", array( "#DIR#" => $tmpDir, "#PERMS#" => self::formatFilePermissions(static::getFilePerm($tmpDir)), )) ); return self::STATUS_FAILED; } /** * Return current system user ID * * @return int|null */ protected static function getCurrentUID() { if(is_callable("getmyuid")) { return getmyuid(); } elseif(is_callable("posix_geteuid")) { return posix_geteuid(); } else { return null; } } /** * Return current system user group ID * * @return int|null */ protected static function getCurrentGID() { if(is_callable("getmygid")) { return getmygid(); } elseif(is_callable("posix_getegid")) { return posix_getegid(); } else { return null; } } /** * Format system user ID, e.g. $uid 0 = root(0) * * @param int $uid * @return string */ protected static function formatUID($uid) { if(is_callable("posix_getpwuid")) { $uid = posix_getpwuid($uid); return sprintf('%s(%s)', $uid['name'], $uid['uid']); } return $uid; } /** * Format system user group ID, e.g. $gid 0 = root(0) * * @param int $gid * @return string */ protected static function formatGID($gid) { if(is_callable("posix_getgrgid")) { $gid = posix_getgrgid($gid); return sprintf('%s(%s)', $gid['name'], $gid['gid']); } return $gid; } protected static function formatFilePermissions($perms) { // http://www.php.net/manual/en/function.fileperms.php if (($perms & 0xC000) == 0xC000) { // Socket $info = 's'; } elseif (($perms & 0xA000) == 0xA000) { // Symbolic Link $info = 'l'; } elseif (($perms & 0x8000) == 0x8000) { // Regular $info = '-'; } elseif (($perms & 0x6000) == 0x6000) { // Block special $info = 'b'; } elseif (($perms & 0x4000) == 0x4000) { // Directory $info = 'd'; } elseif (($perms & 0x2000) == 0x2000) { // Character special $info = 'c'; } elseif (($perms & 0x1000) == 0x1000) { // FIFO pipe $info = 'p'; } else { // Unknown $info = 'u'; } // Owner $info .= (($perms & 0x0100) ? 'r' : '-'); $info .= (($perms & 0x0080) ? 'w' : '-'); $info .= (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-')); // Group $info .= (($perms & 0x0020) ? 'r' : '-'); $info .= (($perms & 0x0010) ? 'w' : '-'); $info .= (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-')); // World $info .= (($perms & 0x0004) ? 'r' : '-'); $info .= (($perms & 0x0002) ? 'w' : '-'); $info .= (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-')); return $info; } /** * Check Bitrix temporary directory path. * * @since 15.5.4 * @return string */ protected function checkBitrixTempPath() { $io = CBXVirtualIo::GetInstance(); $path = CTempFile::GetAbsoluteRoot(); $path = $io->CombinePath($path); $documentRoot = self::getParam("DOCUMENT_ROOT", $_SERVER["DOCUMENT_ROOT"]); $documentRoot = $io->CombinePath($documentRoot); if (strpos($path, $documentRoot) === 0) { $this->addUnformattedDetailError( "SECURITY_SITE_CHECKER_BITRIX_TMP_DIR", CSecurityCriticalLevel::MIDDLE, getMessage("SECURITY_SITE_CHECKER_BITRIX_TMP_DIR_ADDITIONAL", array( "#DIR#" => $path )) ); return static::STATUS_FAILED; } return static::STATUS_PASSED; } }