Роняем 1С-Битрикс с помощью HTML тега

1С-Битрикс это весьма своеобразное явление на российском рынке. Пожалуй уже ни для кого не является открытием, что качество этого продукта с инженерной точки зрения невелико, однако он по-прежнему занимает лидирующие позиции на рынке коммерческой разработки и даже не думает их уступать. Огромное количество готовых решений, сравнительно неплохая документация и мощный отдел маркетинга делают свое дело – Битрикс популярен и является решением “по умолчанию” для запуска корпоративного портала или интернет-магазина.

Временами эта CMS подкидывает невероятные сюрпризы – хотя по-настоящему критические уязвимости, похоже, были закрыты за долгие годы развития системы; периодически встречаются удивительные косяки.

Сегодня мы узнаем, как уронить сайт на битриксе 1С-Битрикс с помощью всего лишь 1 тега HTML!
Отмечу сразу, что не считаю эту проблему критической, так как для того, чтобы это осуществить, необходимо иметь права в системе на правку файлов – т.е не стоит опасаться атаки через эту уязвимость.
Хотя при определенных условиях данная особенность поведения 1С-Битрикс может привести к проблемам для владельца сайта.

В качестве стартовых условий у нас следущее:

  • Коробка битрикс v16.5.4 – релиз 4 июля 2016 года
  • PHP 7 – вышел в декабре 2015, а список того, что изменится был известен и ранее
  • Заявление, что с версии 16.5 появилась поддержка PHP версии 7
  • Работающий около года сайт на указанной платформе

Так что же произошло с нашим сайтом?

Поступила задача подключить к сайту Google Tag Manager. Он подключается с помощью js скрипта, а на случай, если javascript выключен, предлагается запасной вариант в виде iframe.
Выглядит это примерно так:

<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-#YOUR_GTM_ID#"
                 height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->

Вставлен этот iframe был вместе со скриптом в файл header.php шаблона сайта. И тут случилось невероятное – сайт упал.

Call to undefined function mysql_connect() (0)
/site_dir/bitrix/modules/security/classes/mysql/database.php:24
#0: CSecurityDB::Init(boolean)
	/site_dir/bitrix/modules/security/classes/mysql/database.php:103
#1: CSecurityDB::Query(string, string)
	/site_dir/bitrix/modules/security/classes/general/antivirus.php:440
#2: CSecurityAntiVirus->dolog()
	/site_dir/bitrix/modules/security/classes/general/antivirus.php:599
#3: CSecurityAntiVirus->Analyze(string)
	/site_dir/bitrix/modules/security/classes/general/antivirus.php:197
#4: CSecurityAntiVirus::OnEndBufferContent(string)
	/site_dir/bitrix/modules/main/classes/general/module.php:490
#5: ExecuteModuleEventEx(array, array)
	/site_dir/bitrix/modules/main/classes/general/main.php:3254
#6: CAllMain->EndBufferContent(string)
	/site_dir/bitrix/modules/main/classes/general/main.php:3201
#7: CAllMain->EndBufferContentMan()
	/site_dir/bitrix/modules/main/include/epilog_after.php:36
#8: require(string)
	/site_dir/bitrix/modules/main/include/epilog.php:3
#9: require_once(string)
	/site_dir/bitrix/footer.php:4
#10: require(string)
	/site_dir/index.php:662

Что самое интересное – здесь нигде нет упоминания header.php. То есть сейчас мы, конечно, понимаем, чем вызвана ошибка – в конце концов я предупредил о ней в заголовке статьи, но по сообщению ошибки непросто понять, что виновником стал <iframe>.
Наш путь начинается в index.php, мы доходим до подключения footer.php, где весь вывод буферизуется. После чего строка буфера прогоняется через анализатор модуля безопасности и падает на вызове mysql_connect, которая была удалена в php 7, а устаревшей ее объявили аж в 2013 году в версии php 5.5 и сегодня ее можно встретить только в туториалах для новичков конца нулевых-начала десятых (там же в комментариях будут советы использовать mysqli) и в коммерческом продукте стоимостью вплоть до сотен тысяч рублей, который заявляет про себя, что является высокотехнологичным.

Тут у каждого, кто работал с битриксом достаточно долго, чтобы собрать пару-другую граблей возникнет примерно следующая мысль:
Постойте, это очень просто, чтобы Битрикс корректно работал с PHP 7, надо написать в файле dbconn.php вот такую строчку define("BX_USE_MYSQLI", true); И еще в .settings.php настроить подключение к БД – вот так: 'className' => '\Bitrix\Main\DB\MysqliConnection'
Ради этого не стоит писать статью, верно?
Но Битрикс не был бы собой, если бы все действительно этим ограничивалось!

Более подробное рассмотрение трейса ошибки с погружением в исходники дает нам понимание, что же происходит. В принципе ничего особенно сложного: в пункте #3 в процессе анализа CSecurityAntiVirus находит наш iframe в header.php и пытается сделать запись в лог безопасности о потенциальной угрозе. Кстати, iframe в теле страницы его не беспокоит, что в принципе логично, потому что этот элемент в header достаточно экзотическая вещь, тогда как в контентной части периодически требуется такой функционал.

Так вот, в модуле безопасности помимо диктории /mysql/ в этой редакции битрикса нет вообще ничего, что работало бы с базой данных. Поэтому наша настройка define("BX_USE_MYSQLI", true); в конкретном данном случае является профанацией, а любая критическая с точки зрения модуля безопасности проблема, требующая логирования, приведет к печальному концу.
Для работы этого модуля Битрикс по каким-то причинам не стал пользоваться своим собственным $DB и сделал отдельное подключение: $SECURITY_SESSION_DBH = @mysql_connect($DB->DBHost, $DB->DBLogin, $DB->DBPassword, true);
Ну то есть как не стал – здесь разработчики воспользовались для подключения данными, которые уже есть в объекте $DB, выступающий в битриксе в качестве глобального объекта базы данных и который сам по себе является стремным легаси и плохой практикой, особенно в свете выхода нового ядра – но здесь на его основе было создано нечто еще более ужасное, и в конечном счете ломающее всю систему.
Что любопытно – разработчики знали про то, что функция устарела, она кидала warning – и очевидно он раздражал, поэтому проблема warning’a и устаревшего кода тут решена элегантно – одним символом “@”.
Воистину, краткость – сестра таланта!

Что ж, теперь, когда проблема ясна, остается только понять, как ее решить. В методе CSecurityAntiVirus->Analyze происходит проверка контента – и в частности в нашем случае проверяются источники фреймов.
Это маленький кусочек проверки:

function isInWhiteList()
{
  if(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(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;
 
... много кода

Т.е у битрикса есть некоторое количество доверенных источников, которые он считает безопасными, а если атрибут src не входит в их число, то делается запись в журнал безопасности для информирования системного администратора
Первой идеей было пропатчить это, однако в случае битрикса трогать ядро почти всегда плохая идея – зато практически любую вещь можно настроить через административную часть. В данном случае такая возможность тоже оказалась.
Переходим по адресу /bitrix/admin/security_antivirus.php?lang=ru&tabControl_active_tab=exceptions – и там будет место, куда можно вписать доверенные URL для ваших ресурсов. Битрикс не будет на них ругаться. В нашем случае добавление адреса googletagmanager помогло решить проблему

Какой можно сделать вывод?

Эту уязвимость действительно не является критической, как я уже написал, потому как имея доступ к php-файлам моржно сделать куда более серьезные вещи. Однако в случае если подобными тривиальными правками занимается не очень квалифицированный программист – каких раньше называли вебмастерами, либо вообще человек далекий от программирования, который прошел пару курсов и занимается поддержкой контента и доработками методом copy-paste, внезапно упавший сайт будет неприятным сюрпризом. Действительно нередко владельцы бизнеса любят сэкономить на IT, а усилиями маркетологов битрикс и вовсе считается системой, где программист не нужен вообще (очень опасное заблуждение).

Сами представители битрикс в ответ на мое гневное письмо в поддержку посоветовали обновиться. Это стандартный ответ почти на любые вопросы в ТП от битрикса, но в этот раз ответ был в тему: новейшая на тот момент версия 17 не содержала более этого файла,
модуль безопасности был переработан. Так что наблюдаемая проблема не встретится пользователям новых версий.

Что можно сказать в заключение? Во-первых, 1С-Битрикс очень большая и неповоротливая система. Хотя в некоторых ее местах наводят порядок, есть огромная масса кода, которая осталась со времен динозавров. И в повседневной работе стоит учитывать этот момент и быть готовым к разного рода неожиданностям.
Во-вторых битрикс надо обновлять. Это кажется трюизмом, однако действительно, если есть возможность обновиться, лучше это сделать. У Битрикса огромное количество пользователей и они находят самые диковинные и невообразимые ошибки, которые затем исправляются в следующих релизах. Доказательством этого может быть хотя бы то, что в следующем мажорном релизе описанную мною ошибку исправили.

Ну и еще один вывод, который скорее является темой для холивара или беседы в пятницу вечером в баре: лучше всего с Битриксом не работать вообще. Впрочем, для работающих на заказ, либо в сегменте e-commerce это больше похоже на утопию, нежели на реальный совет.