replace=1; $Antivirus->Analyze($s); echo htmlspecialcharsbx($s),'
',htmlspecialcharsbx(print_r($Antivirus,1));
*/

class CSecurityAntiVirus
{
	var $place = "";
	var $stylewithiframe = false;

	// this properties may be changed after object creation
	var $maxrating = 20; //рейтинг принятия решений
	var $useglobalrules = 1; //использовать глобальные правила

	var $replace = 1;//
	var $replacement = ""; //на что заменяем, если заменяем..

	//результаты
	var $resultrules; //массив сработавших правил
	var $whitelist_id;

	//вспомогательные свойства
	var $data = ''; //полный код блока, включая ограничивающие теги
	var $type = ''; //тип блока
	var $body = ''; // тело блока.
	var $bodylines = false; // массив строк из body
	var $bodyWOquotes = '';

	var $atributes = ''; // дополнительные атрибуты (вместе с src)

	var $cnt = 0; //счетчик обработанных блоков

	var $prev = '';
	var $next = '';

	private $quotes = array();

	function __construct($place = "body")
	{
		$this->place = $place;
		global $BX_SECURITY_AV_ACTION;
		if($BX_SECURITY_AV_ACTION === "notify_only")
			$this->replace = false;
	}

	public static function IsActive()
	{
		$bActive = false;
		foreach(GetModuleEvents("main", "OnPageStart", true) as $event)
		{
			if(
				isset($event["TO_MODULE_ID"]) && $event["TO_MODULE_ID"] === "security"
				&& isset($event["TO_CLASS"]) && $event["TO_CLASS"] === "CSecurityAntiVirus"
			)
			{
				$bActive = true;
				break;
			}
		}
		return $bActive;
	}

	public static function SetActive($bActive = false)
	{
		if($bActive)
		{
			if(!CSecurityAntiVirus::IsActive())
			{
				//Just pre compression
				RegisterModuleDependences("main", "OnPageStart", "security", "CSecurityAntiVirus", "OnPageStart", -1);
				RegisterModuleDependences("main", "OnEndBufferContent", "security", "CSecurityAntiVirus", "OnEndBufferContent", 10000);
				//Right after compression
				RegisterModuleDependences("main", "OnAfterEpilog", "security", "CSecurityAntiVirus", "OnAfterEpilog", 10001);
			}
		}
		else
		{
			if(CSecurityAntiVirus::IsActive())
			{
				UnRegisterModuleDependences("main", "OnPageStart", "security", "CSecurityAntiVirus", "OnPageStart");
				UnRegisterModuleDependences("main", "OnEndBufferContent", "security", "CSecurityAntiVirus", "OnEndBufferContent");
				UnRegisterModuleDependences("main", "OnAfterEpilog", "security", "CSecurityAntiVirus", "OnAfterEpilog");
			}
		}
	}

	public static function GetAuditTypes()
	{
		return array(
			"SECURITY_VIRUS" => "[SECURITY_VIRUS] ".GetMessage("SECURITY_VIRUS"),
		);
	}

	public static function OnPageStart()
	{
		if (CSecuritySystemInformation::isCliMode())
			return;

		if (self::isSafetyRequest()) //Check only GET and POST request
			return;

		global $APPLICATION, $DB, $BX_SECURITY_AV_TIMEOUT, $BX_SECURITY_AV_ACTION;
		$BX_SECURITY_AV_TIMEOUT = COption::GetOptionInt("security", "antivirus_timeout");
		$BX_SECURITY_AV_ACTION = COption::GetOptionInt("security", "antivirus_action");

		//user white list
		global $BX_SECURITY_AV_WHITE_LIST, $CACHE_MANAGER;
		if($CACHE_MANAGER->Read(36000, "b_sec_white_list"))
		{
			$BX_SECURITY_AV_WHITE_LIST = $CACHE_MANAGER->Get("b_sec_white_list");
		}
		else
		{
			$BX_SECURITY_AV_WHITE_LIST = array();
			$res = CSecurityAntiVirus::GetWhiteList();
			while($ar = $res->Fetch())
				$BX_SECURITY_AV_WHITE_LIST[] = $ar["WHITE_SUBSTR"];
			$CACHE_MANAGER->Set("b_sec_white_list", $BX_SECURITY_AV_WHITE_LIST);
		}

		//Init DB in order to be able to register the event in the shutdown function
		CSecurityDB::Init();

		//Check if we started output buffering in auto_prepend_file
		//so we'll have chances to detect virus before prolog
		if(defined("BX_SECURITY_AV_STARTED"))
		{
			$content = ob_get_contents();
			ob_end_clean();
			if($content <> '')
			{
				$Antivirus = new CSecurityAntiVirus("pre");
				$Antivirus->Analyze($content);
				echo $content;
			}
		}

		//Initiate monitoring of output that can be after working antivirus.
		register_shutdown_function(array('CSecurityAntiVirus', 'PHPShutdown'));

		//Check notification from previous hit
		$fname = $_SERVER["DOCUMENT_ROOT"].BX_PERSONAL_ROOT."/managed_cache/b_sec_virus";
		if(file_exists($fname))
		{
			$rsInfo = $DB->Query("select *, UNIX_TIMESTAMP(TIMESTAMP_X) as ts_x from b_sec_virus where SENT='N'");
			if($arInfo = $rsInfo->Fetch())
			{
				if($table_lock = CSecurityDB::LockTable('b_sec_virus', $APPLICATION->GetServerUniqID()."_virus"))
				{
					$SITE_ID = false;
					do {
						$SITE_ID = $arInfo["SITE_ID"];
						if($arInfo["INFO"] <> '')
						{
							$arEvent = unserialize(base64_decode($arInfo["INFO"]), ['allowed_classes' => false]);
							if(is_array($arEvent))
							{
								$arEvent["TIMESTAMP_X"] = ConvertTimeStamp($arInfo["ts_x"], "FULL");
								$DB->Add("b_event_log", $arEvent, array("DESCRIPTION"));
							}
						}
						CSecurityDB::Query("update b_sec_virus set SENT='Y' where ID='".$arInfo["ID"]."'", '');
					} while ($arInfo = $rsInfo->Fetch());

					CTimeZone::Disable();
					$arDate = localtime(time());
					$date = mktime($arDate[2], $arDate[1]-$BX_SECURITY_AV_TIMEOUT, 0, $arDate[4]+1, $arDate[3], 1900+$arDate[5]);
					CSecurityDB::Query("DELETE FROM b_sec_virus WHERE TIMESTAMP_X <= ".$DB->CharToDateFunction(ConvertTimeStamp($date, "FULL")), '');
					CTimeZone::Enable();

					CEvent::Send("VIRUS_DETECTED", $SITE_ID? $SITE_ID: SITE_ID, array("EMAIL" => COption::GetOptionString("main", "email_from", "")));

					CSecurityDB::UnlockTable($table_lock);

					@unlink($fname);
				}
			}
		}
	}

	public static function OnEndBufferContent(&$content)
	{
		if (self::isSafetyRequest()) //Check only GET and POST request
			return;

		//Обработка основного вывода
		$Antivirus = new CSecurityAntiVirus("body");
		$Antivirus->Analyze($content);
	}

	public static function OnAfterEpilog()
	{
		if (self::isSafetyRequest()) //Check only GET and POST request
			return;

		//start monitoring of output that can be after working antivirus.
		ob_start();
		define("BX_SECURITY_AV_AFTER_EPILOG", true);
	}

	public static function PHPShutdown()
	{
		if(defined("BX_SECURITY_AV_AFTER_EPILOG"))
		{
			$content = ob_get_contents();
			if($content <> '')
			{
				ob_end_clean();

				if(mb_substr($content, 0, 6) == "" && preg_match("#\\s*\$#is", $content))
				{
					$Antivirus = new CSecurityAntiVirus("body");
				}
				else
				{
					$Antivirus = new CSecurityAntiVirus("post");
				}

				$Antivirus->Analyze($content);
				echo $content;
			}
		}
	}

	public static function GetWhiteList()
	{
		global $DB;
		$res = $DB->Query("SELECT * FROM b_sec_white_list ORDER BY ID", false, "FILE: ".__FILE__."
LINE: ".__LINE__); return $res; } public static function UpdateWhiteList($arWhiteList) { global $DB, $CACHE_MANAGER; $DB->Query("DELETE FROM b_sec_white_list", false, "FILE: ".__FILE__."
LINE: ".__LINE__); $i = 1; foreach($arWhiteList as $white_str) { $white_str = trim($white_str); if($white_str) $DB->Add("b_sec_white_list", array("ID" => $i++, "WHITE_SUBSTR" => $white_str)); } $CACHE_MANAGER->Clean("b_sec_white_list"); } // function returns 1, if current block is in white list and needs not processing. function isInWhiteList() { if(mb_strpos($this->atributes, 'src="/bitrix/') !== false) return 1; if(preg_match('#src="http[s]?://(api-maps\\.yandex|maps\\.google|apis\\.google|stg\\.odnoklassniki)\\.[a-z]{2,3}/#', $this->atributes)) return 2; if(mb_strpos($this->body, 'BX_DEBUG_INFO') !== false) return 3; if(preg_match('#(google-analytics\\.com/ga\\.js|openstat\\.net/cnt\\.js|autocontext\\.begun\\.ru/autocontext\\.js|counter\\.yadro\\.ru/hit)#', $this->body)) return 4; if(preg_match('/var\s+(cmt|jsMnu_toolbar_|hint|desktopPage|arStructure|current_selected|arCrmSelected|arKernelCSS|lastUsers|arStore)/', $this->body)) return 5; if(preg_match('/(arFDDirs|arFDFiles|arPropFieldsList|PROP)\[/', $this->body)) return 6; if(preg_match('/(addPathRow|MoveProgress|Import|DoNext|JCMenu|AttachFile|CloseDialog|_processData|showComment|ShowWarnings|SWFObject|deliveryCalcProceed|structReload|addForumImagesShow|rsasec_form_bind|BX_YMapAddPolyline|BX_YMapAddPlacemark|CloseWaitWindow|DoChangeExternalSaleId|AjaxSend|readFileChunk|EndDump|createMenu|addProperty)\(/', $this->body)) return 7; if(mb_strpos($this->body, 'window.operation_success = true;') !== false) return 8; if(preg_match('/(jsAjaxUtil|jsUtils|jsPopup|elOnline|jsAdminChain|jsEvent|jsAjaxHistory|bxSession|BXHotKeys|oSearchDialog)\./', $this->body)) return 9; if(preg_match('/new\s+(PopupMenu|JCAdminFilter|JCSmartFilter|JCAdminMenu|BXHint|ViewTabControl|BXHTMLEditor|JCTitleSearch|JCWDTitleSearch|BxInterfaceForm|Date|JCEmployeeSelectControl|JCCatalogBigdataProducts|JCCatalogSection|JCCatalogElement|JCCatalogTopSlider|JCCatalogTopSection|JCCatalogSectionRec|JCCatalogSectionViewed|JCCatalogCompareList|JCCatalogItem|JCSaleGiftProduct|B24\.SearchTitle)/', $this->body)) return 10; if(mb_strpos($this->body, 'document\.write(\'body)) return 29; if(preg_match('/\.setTimeout\(\'CheckNew\(\)\'/', $this->body)) return 30; if(preg_match('/function\s+twitter_click_\d+\(longUrl\)/', $this->body)) return 31; if(preg_match('/(window\.)*parent\.document\.getElementById\(["\'](COUNTERS_UPDATED|div_PROPERTY_DEFAULT_VALUE)["\']\)\.innerHTML/',$this->body)) return 32; if(preg_match('/(TasksUsers|IntranetUsers).arEmployees/',$this->body)) return 35; if(preg_match('/window\.location\s*=\s*[\'"]\/bitrix\/admin\/iblock_bizproc_workflow_edit.php/', $this->body)) return 36; if(preg_match('/window\.parent\.location\.href\s*=\s*[\'"]\/bitrix\/admin\/sale_order_new.php/', $this->body)) return 43; if(preg_match('/^window\.open\(/', $this->body)) return 44; if(preg_match('/^\s*window\.__bxResult\[\'\d+\'\]\s*=\s*\{/', $this->body)) return 46; if(mb_strpos($this->body, 'showFLVPlayer') !== false) return 37; if(preg_match('/var\s+formSettingsDialogCRM_(LEAD|DEAL|COMPANY|CONTACT)_SHOW/', $this->body)) return 38; if(preg_match('/parent\.(FILE_UPLOADER_CALLBACK)/', $this->body)) return 39; if(preg_match('/bxForm_CRM/', $this->body)) return 40; if(preg_match('/\$\(([\'"])[^\'"]*[\'"]\)/', $this->body)) return 41; if(preg_match('/document\.documentElement\.className/i', $this->body)) return 42; //site checker if(preg_match('/var\s*fix_mode\s*=/i', $this->body)) return 43; //Voximplant && powerBi && gtm if($this->type == 'iframe' && preg_match('#\s*src=[\'"]https://(verify\.voximplant\.com|lookerstudio\.google\.com|datastudio\.google\.com|app\.powerbi\.com|www\.googletagmanager\.com)/#i', $this->atributes)) return 45; if(preg_match('#function\s+bizvalChange#', $this->body)) return 46; if($this->type === "script") { if(preg_match('#type="application/json"#is', $this->atributes)) return 44; if(preg_match('#type="application/ld\+json"#is', $this->atributes)) return 44; if(preg_match('#type="text/x-template"#is', $this->atributes)) return 44; $filter = new CSecurityXSSDetect(array("action" => "none", "log" => "N")); $this->bodyWOquotes = trim($filter->removeQuotedStrings($this->body, false), " \t\n\r"); $this->bodyWOquotes = preg_replace("/\\s*(window\\.top|top|window|window\\.document|document)\\.(strWarning|location\\.href|location|action_warning|__bx_res_sn_filename|title|title[\\d]+\\s*=\\s*title[\\d]+|text[\\d]+\\s*=\\s*text[\\d]+)\\s*=\\s*(|\\s*\\+\\s*)+;{0,1}\\s*/s", "", $this->bodyWOquotes, -1, $count); $this->bodyWOquotes = preg_replace("/\\s*(alert|SelFile)\\s*\\((|[0-9]+|\\s*\\+\\s*)+\\)\\s*;{0,1}\\s*/", "", $this->bodyWOquotes); $this->bodyWOquotes = trim($this->bodyWOquotes, "\n\r\t "); $this->bodyWOquotes = preg_replace("/^\\/\\/[^\n]*\$/", "", $this->bodyWOquotes); if($this->bodyWOquotes === "") return 33; } //user defined white list global $BX_SECURITY_AV_WHITE_LIST; if(is_array($BX_SECURITY_AV_WHITE_LIST)) foreach($BX_SECURITY_AV_WHITE_LIST as $white_substr) if(mb_strpos($this->data, $white_substr) !== false) return 34; return 0; } //заглушка. Возщвращает рейтинг опасности текущего блока из кеша, или FALSE // кешируются только составляющся рейтинга, вложденная внутренними правилами. function returnfromcache() { // тут можно вставить кеширование. Для кеширование вычислять и сохранять кеш от $this->data return false; } //заглушка. Добавляет рейтинг опасности для текущего блока в кеш. function addtocache() { // тут можно вставить кеширование. Для кеширование вычислять и сохранять кеш от $this->data return true; } //механизм для вывода сообщения об обнаруженном подозрительном текущем блоке function dolog() { global $BX_SECURITY_AV_TIMEOUT; if(defined("ANTIVIRUS_CREATE_TRACE")) $this->CreateTrace(); $uniq_id = md5($this->data); $rsLog = CSecurityDB::Query("SELECT * FROM b_sec_virus WHERE ID = '".$uniq_id."'", "Module: security; Class: CSecurityAntiVirus; Function: AddEventLog; File: ".__FILE__."; Line: ".__LINE__); $arLog = CSecurityDB::Fetch($rsLog); if($arLog && ($arLog["SENT"] == "Y")) { CSecurityDB::Query("DELETE FROM b_sec_virus WHERE SENT = 'Y' AND TIMESTAMP_X < ".CSecurityDB::SecondsAgo($BX_SECURITY_AV_TIMEOUT*60)."", "Module: security; Class: CSecurityAntiVirus; Function: AddEventLog; File: ".__FILE__."; Line: ".__LINE__); $rsLog = CSecurityDB::Query("SELECT * FROM b_sec_virus WHERE ID = '".$uniq_id."'", "Module: security; Class: CSecurityAntiVirus; Function: AddEventLog; File: ".__FILE__."; Line: ".__LINE__); $arLog = CSecurityDB::Fetch($rsLog); } if(!$arLog) { $ss = $this->data; if(defined("ANTIVIRUS_CREATE_TRACE")) foreach($this->resultrules as $k=>$v) $ss .= "\n".$k."=".$v; if(defined("SITE_ID") && !defined("ADMIN_SECTION")) { $SITE_ID = SITE_ID; } else { $rsDefSite = CSecurityDB::Query("SELECT LID FROM b_lang WHERE ACTIVE='Y' ORDER BY DEF desc, SORT", "Module: security; Class: CSecurityAntiVirus; Function: AddEventLog; File: ".__FILE__."; Line: ".__LINE__); $arDefSite = CSecurityDB::Fetch($rsDefSite); if($arDefSite) $SITE_ID = $arDefSite["LID"]; else $SITE_ID = false; } $s = serialize(array( "SEVERITY" => "SECURITY", "AUDIT_TYPE_ID" => "SECURITY_VIRUS", "MODULE_ID" => "security", "ITEM_ID" => "UNKNOWN", "REMOTE_ADDR" => $_SERVER["REMOTE_ADDR"], "USER_AGENT" => $_SERVER["HTTP_USER_AGENT"], "REQUEST_URI" => $_SERVER["REQUEST_URI"], "SITE_ID" => defined("SITE_ID")? SITE_ID: false, "USER_ID" => false, "GUEST_ID" => array_key_exists("SESS_GUEST_ID", $_SESSION) && ($_SESSION["SESS_GUEST_ID"] > 0)? $_SESSION["SESS_GUEST_ID"]: false, "DESCRIPTION" => "==".base64_encode($ss), )); CSecurityDB::QueryBind( "insert into b_sec_virus (ID, TIMESTAMP_X, SITE_ID, INFO) values ('".$uniq_id."', ".CSecurityDB::CurrentTimeFunction().", ".($SITE_ID? "'".$SITE_ID."'": "null").", :INFO)", array("INFO" => base64_encode($s)), "Module: security; Class: CSecurityAntiVirus; Function: AddEventLog; File: ".__FILE__."; Line: ".__LINE__ ); @fclose(@fopen($_SERVER["DOCUMENT_ROOT"].BX_PERSONAL_ROOT."/managed_cache/b_sec_virus","w")); } } // вызывается каждый раз, когда обработка блока закончена и блок признан нормальным. // функция должна возвратить содержимое блока. function end_okblock() { return $this->data; } function end_whiteblock() { return $this->data; } // вызывается каждый раз, когда обработка блока закончена и блок признан опасным. // функция должна возвратить содержимое блока. function end_blkblock() { if($this->replace) return $this->replacement; else return $this->data; } function CreateTrace() { $cache_id = md5($this->data); $fn = $_SERVER["DOCUMENT_ROOT"]."/bitrix/cache/virus.db/".$cache_id.".vir"; if(!file_exists($fn)) { CheckDirPath($fn); $f = fopen($fn, "wb"); fwrite($f, $this->data); fwrite($f, "\n------------------------------\n\$_SERVER:\n"); foreach($_SERVER as $k=>$v) fwrite($f, $k." = ".$v."\n"); fwrite($f, "\n------------------------------\n\$this->resultrules:\n"); foreach($this->resultrules as $k=>$v) fwrite($f, $k." = ".$v."\n"); fclose($f); @chmod($fn, BX_FILE_PERMISSIONS); } } function Analyze(&$content) { static $arLocalCache = array(); $content_len = strlen($content) * 2; Ini::adjustPcreBacktrackLimit($content_len); $this->stylewithiframe = preg_match("/\s*iframe/", $content); $arData = preg_split("/(.*?<\\/script.*?>|.*?<\\/iframe.*?>)/is", $content, -1, PREG_SPLIT_DELIM_CAPTURE); $cData = is_array($arData) ? count($arData) : 0; if($cData < 2) return; $bDataChanged = false; for($iData = 1; $iData < $cData; $iData += 2) { $this->data = $arData[$iData]; //полный код блока, включая ограничивающие теги // < 1 2 > 3 4 if(!preg_match('/^<(script|iframe)(.*?)>(.*?)(<\\/\\1.*?>)$/is', $this->data, $ret)) continue; if($iData>1) $this->prev = $arData[$iData-2].$arData[$iData-1]; else $this->prev = $arData[$iData-1]; if($iData < $cData-2) $this->next = $arData[$iData+1].$arData[$iData+2]; else $this->next = $arData[$iData+1]; $this->resultrules = array(); $this->bodylines = false; $this->atributes = $ret[2]; if(mb_strtolower($ret[1]) == 'script') { $this->body = $this->returnscriptbody($this->data); $this->type = 'script'; } else { $this->body = ''; $this->type = 'iframe'; } $this->whitelist_id = $this->isInWhiteList(); if(!$this->whitelist_id) { $cache_id = md5($this->data); if(!isset($arLocalCache[$cache_id])) $arLocalCache[$cache_id] = $this->returnblockrating(); if($arLocalCache[$cache_id] >= $this->maxrating) { $this->dolog(); $arData[$iData] = $this->end_blkblock(); if($this->replace) $bDataChanged = true; } } $this->cnt++; } if($bDataChanged) $content = implode('', $arData); } /* Возвращает рейтинг опасности блока (ифрейм или скрипт) входные параметры класса должны быть заполнеы. */ function returnblockrating() { if($this->type=='iframe') { if(!preg_match("/src=[\'\"]?http/", $this->atributes)) return 0; } $r = $this->returnfromcache(); if($r === false) { $r = 0; //вначале все кешируемые внутренние правила if($this->type=='iframe') { $r += $this->ruleframevisiblity(); } elseif($this->type=='script') { $r += $this->rulescriptbasics(); $r += $this->rulescriptvbscript(); $r += $this->rulescriptwhiterules(); $r += $this->rulescriptnamerules(); } $r += $this->ruleallsources(); $this->addtocache($r); } // некешируемые наружные правила.. $r += $this->rulescriptglobals(); $r += $this->rulescriptblocks(); return $r; } // ПРАВИЛА // надбавки и скидки действующие для каждого скрипта (возможно с некоторыми условиями) function rulescriptglobals() { return 0; $r = 0; if(!$this->useglobalrules) { return 0; } if($this->type=='script' && $this->stylewithiframe ) { $val = 4; $r += $val; $this->resultrules['rulescriptglobals_styleiframe'] = $val; } if($this->place == "post") { $val = 12; $r += $val; $this->resultrules['rulescriptglobals_blockafterend'] = $val; } if($this->place == "pre") { $val = 12; $r += $val; $this->resultrules['rulescriptglobals_blockprestart'] = $val; } return $r; } //правила, учитывающие окружение скрипта function rulescriptblocks() { $r = 0; $strp = preg_replace('/$/', '', $this->prev); $strn = preg_replace('/^/', '', $this->next); //удалили окружающие комментарии, если имелись.. if($this->cnt == 0) //обрабатывается первое попадание... { if(preg_match("/^\s*$/is", $strp)) { $val = 1; $r += $val; $this->resultrules['rulescriptblocks_blockinstart'] = $val; } } if(preg_match("/^\s*$/is", $strn)) { $val = 1; $r += $val; $this->resultrules['rulescriptblocks_endofhtml'] = $val; } if(preg_match("/]*?>\s*$/is", $strp)) { $val = 3; $r += $val; $this->resultrules['rulescriptblocks_postbody'] = $val; } if(preg_match("/^\s*<\\/body[^>]*?>/is", $strn)) { $val = 3; $r += $val; $this->resultrules['rulescriptblocks_preendofbody'] = $val; } if(preg_match("/<\\/html[^>]*?>\s*$/is", $strp)) { $val = 10; $r += $val; $this->resultrules['rulescriptblocks_postendofhtml'] = $val; } if($this->type == 'iframe') { if(preg_match("/]+((visibility\s*:\s*hidden)|(display\s*:\s*none))[^>]*>\s*$/is", $strp)) { $val = 11; $r += $val; $this->resultrules['rulescriptblocks_inhideddiv'] = $val; } } if(preg_match("/^\s*