Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2026-06-17 v6.8.1
- Исправлены замечания по безопасности модуля. Устранены уязвимости после проверки приложения на Маркетплейсе.

## 2026-06-16 v6.8.0
- Исправлены замечания по безопасности модуля. Устранены уязвимости Authentication Bypass, IDOR и SSRF. Снижены риски CSRF, XSS и Information Disclosure. Усилены проверки доступа и обработки пользовательских данных.

Expand Down
28 changes: 23 additions & 5 deletions intaro.retailcrm/classes/general/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,19 @@ public function write($dump, $file = 'info')
{
$rsSites = CSite::GetList($by, $sort, array('DEFAULT' => 'Y'));
$ar = $rsSites->Fetch();
$logDir = $ar['ABS_DOC_ROOT'] . $this->logPath . '/';

if (!is_dir($ar['ABS_DOC_ROOT'] . $this->logPath . '/')) {
mkdir($ar['ABS_DOC_ROOT'] . $this->logPath . '/');
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$file = $ar['ABS_DOC_ROOT'] . $this->logPath . '/' . $file . '.log';

$file = $logDir . $file . '.log';

$data['TIME'] = date('Y-m-d H:i:s');
$data['DATA'] = $dump;

$f = fopen($file, "a+");
fwrite($f, print_r($data, true));
fwrite($f, $this->maskSensitiveData(print_r($data, true)));
fclose($f);

// if filesize more than 5 Mb rotate it
Expand Down Expand Up @@ -119,4 +120,21 @@ private function clean($file)
{
file_put_contents($file, '');
}

private function maskSensitiveData($data)
{
$sensitiveKeys = 'api[_-]?key|token|access[_-]?token|password|passwd|secret|authorization';

$data = preg_replace(
'/(\[(?:' . $sensitiveKeys . ')\]\s*=>\s*)[^\r\n]*/i',
'$1[masked]',
$data
);

return preg_replace(
'/([?&](?:' . $sensitiveKeys . ')=)[^&\s"\']+/i',
'$1[masked]',
$data
);
}
}
7 changes: 5 additions & 2 deletions intaro.retailcrm/classes/general/RCrmActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,10 @@ public static function eventLog($auditType, $itemId, $description)
public static function uploadOrdersAgent()
{
RetailCrmOrder::uploadOrders();
$failedIds = unserialize(COption::GetOptionString(self::$MODULE_ID, self::$CRM_ORDER_FAILED_IDS, 0));
$failedIds = unserialize(
COption::GetOptionString(self::$MODULE_ID, self::$CRM_ORDER_FAILED_IDS, 0),
['allowed_classes' => false]
);

if (is_array($failedIds) && !empty($failedIds)) {
RetailCrmOrder::uploadOrders(50, true);
Expand Down Expand Up @@ -426,7 +429,7 @@ public static function unserializeArrayRecursive($string)
}

if (is_string($string)) {
$string = unserialize($string);
$string = unserialize($string, ['allowed_classes' => false]);
}

if (!is_array($string)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class RetailCrmCollector
*/
public static function add()
{
$keys = unserialize(COption::GetOptionString(self::$MODULE_ID, self::$CRM_COLL_KEY, 0));
$keys = unserialize(
COption::GetOptionString(self::$MODULE_ID, self::$CRM_COLL_KEY, 0),
['allowed_classes' => false]
);
$collector = COption::GetOptionString(self::$MODULE_ID, self::$CRM_COLL, 0);
$request = \Bitrix\Main\Context::getCurrent()->getRequest();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ class RetailCrmOnlineConsultant
public static function add()
{
$request = \Bitrix\Main\Context::getCurrent()->getRequest();
$script = RetailcrmConfigProvider::getOnlineConsultantScript();

if (RetailcrmConfigProvider::isOnlineConsultantEnabled() && $request->isAdminSection() !== true) {
if (
RetailcrmConfigProvider::isOnlineConsultantEnabled()
&& $request->isAdminSection() !== true
&& $script !== ''
) {
\Bitrix\Main\Page\Asset::getInstance()->addString(
RetailcrmConfigProvider::getOnlineConsultantScript(),
$script,
true
);

Expand Down
5 changes: 4 additions & 1 deletion intaro.retailcrm/classes/general/ua/RetailCrmUa.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ class RetailCrmUa
public static function add()
{
$ua = COption::GetOptionString(self::$MODULE_ID, self::$CRM_UA, 0);
$uaKeys = unserialize(COption::GetOptionString(self::$MODULE_ID, self::$CRM_UA_KEYS, 0));
$uaKeys = unserialize(
COption::GetOptionString(self::$MODULE_ID, self::$CRM_UA_KEYS, 0),
['allowed_classes' => false]
);
$request = \Bitrix\Main\Context::getCurrent()->getRequest();

if ($ua === 'Y' && !empty($uaKeys[SITE_ID]['ID']) && !empty($uaKeys[SITE_ID]['INDEX']) && $request->isAdminSection() !== true) {
Expand Down
2 changes: 1 addition & 1 deletion intaro.retailcrm/description.ru
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- Исправлены замечания по безопасности модуля. Устранены уязвимости Authentication Bypass, IDOR и SSRF. Снижены риски CSRF, XSS и Information Disclosure. Усилены проверки доступа и обработки пользовательских данных.
- Исправлены замечания по безопасности модуля. Устранены уязвимости после проверки приложения на Маркетплейсе.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
die();
}

$sanitizer = new CBXSanitizer();
$sanitizer->SetLevel(CBXSanitizer::SECURE_LEVEL_MIDDLE);

$agreementLoyaltyProgram = $sanitizer->SanitizeHtml((string) ($arResult['AGREEMENT_LOYALTY_PROGRAM'] ?? ''));
$agreementPersonalData = $sanitizer->SanitizeHtml((string) ($arResult['AGREEMENT_PERSONAL_DATA'] ?? ''));

if ($arResult["SHOW_SMS_FIELD"] == true) {
CJSCore::Init('phone_auth');
}
Expand All @@ -34,10 +40,10 @@
?>
<?php CUtil::InitJSCore(['ajax', 'jquery', 'popup']); ?>
<div id="uf_agree_pl_intaro_popup" style="display:none;">
<?=$arResult['AGREEMENT_LOYALTY_PROGRAM']?>
<?=$agreementLoyaltyProgram?>
</div>
<div id="uf_pd_proc_pl_intaro_popup" style="display:none;">
<?=$arResult['AGREEMENT_PERSONAL_DATA']?>
<?=$agreementPersonalData?>
</div>
<script>
BX.ready(function() {
Expand Down Expand Up @@ -96,7 +102,7 @@
<?php $this->addExternalJs(SITE_TEMPLATE_PATH . '/script.js'); ?>
<div id="regBody">
<?php if (isset($arResult['LP_REGISTER']['msg'])) { ?>
<div id="lpRegMsg" class="lpRegMsg"><?=$arResult['LP_REGISTER']['msg']?></div>
<div id="lpRegMsg" class="lpRegMsg"><?=htmlspecialcharsbx((string) $arResult['LP_REGISTER']['msg'])?></div>
<?php } ?>

<?php
Expand Down
18 changes: 9 additions & 9 deletions intaro.retailcrm/install/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ function DoInstall()
COption::SetOptionString($this->MODULE_ID, Constants::CRM_API_KEY_OPTION, $api_key);

if ($sites_list = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_SITES_LIST, 0)) {
$arResult['SITES_LIST'] = unserialize($sites_list);
$arResult['SITES_LIST'] = unserialize($sites_list, ['allowed_classes' => false]);
}

$APPLICATION->IncludeAdminFile(
Expand Down Expand Up @@ -453,19 +453,19 @@ function DoInstall()
$arResult['bitrixStatusesList'] = RCrmActions::StatusesList();

if ($order_types = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_ORDER_TYPES_ARR, 0)) {
$arResult['ORDER_TYPES'] = array_flip(unserialize($order_types));
$arResult['ORDER_TYPES'] = array_flip(unserialize($order_types, ['allowed_classes' => false]));
}
if ($delivery_types = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_DELIVERY_TYPES_ARR, 0)) {
$arResult['DELIVERY_TYPES'] = array_flip(unserialize($delivery_types));
$arResult['DELIVERY_TYPES'] = array_flip(unserialize($delivery_types, ['allowed_classes' => false]));
}
if ($payment_types = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_PAYMENT_TYPES, 0)) {
$arResult['PAYMENT_TYPES'] = array_flip(unserialize($payment_types));
$arResult['PAYMENT_TYPES'] = array_flip(unserialize($payment_types, ['allowed_classes' => false]));
}
if ($payment_statuses = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_PAYMENT_STATUSES, 0)) {
$arResult['PAYMENT_STATUSES'] = array_flip(unserialize($payment_statuses));
$arResult['PAYMENT_STATUSES'] = array_flip(unserialize($payment_statuses, ['allowed_classes' => false]));
}
if ($payment = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_PAYMENT, 0)) {
$arResult['PAYMENT'] = array_flip(unserialize($payment));
$arResult['PAYMENT'] = array_flip(unserialize($payment, ['allowed_classes' => false]));
}

$APPLICATION->IncludeAdminFile(
Expand Down Expand Up @@ -668,13 +668,13 @@ function DoInstall()
}

if ($orderProps = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_ORDER_PROPS, 0)) {
$arResult['ORDER_PROPS'] = unserialize($orderProps);
$arResult['ORDER_PROPS'] = unserialize($orderProps, ['allowed_classes' => false]);
}
if ($legalDetails = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_LEGAL_DETAILS, 0)) {
$arResult['LEGAL_DETAILS'] = unserialize($legalDetails);
$arResult['LEGAL_DETAILS'] = unserialize($legalDetails, ['allowed_classes' => false]);
}
if ($contragentType = COption::GetOptionString($this->OLD_MODULE_ID, Constants::CRM_CONTRAGENT_TYPE, 0)) {
$arResult['CONTRAGENT_TYPES'] = unserialize($contragentType);
$arResult['CONTRAGENT_TYPES'] = unserialize($contragentType, ['allowed_classes' => false]);
}

$APPLICATION->IncludeAdminFile(
Expand Down
4 changes: 2 additions & 2 deletions intaro.retailcrm/install/version.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

$arModuleVersion = [
'VERSION' => '6.8.0',
'VERSION_DATE' => '2026-06-16 12:00:00'
'VERSION' => '6.8.1',
'VERSION_DATE' => '2026-06-17 12:00:00'
];
66 changes: 63 additions & 3 deletions intaro.retailcrm/lib/component/configprovider.php
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,7 @@ protected static function setOption($name, $value = "", $site = ""): void
*/
protected static function getUnserializedOption($option, $def = 0)
{
return unserialize(static::getOption($option, $def));
return unserialize(static::getOption($option, $def), ['allowed_classes' => false]);
}

/**
Expand All @@ -997,7 +997,9 @@ public static function isOnlineConsultantEnabled(): bool

public static function getOnlineConsultantScript(): string
{
return trim(static::getOption(Constants::CRM_ONLINE_CONSULTANT_SCRIPT, ""));
return static::sanitizeOnlineConsultantScript(
trim(static::getOption(Constants::CRM_ONLINE_CONSULTANT_SCRIPT, ""))
);
}

public static function isEventTrackerEnabled(): bool
Expand All @@ -1023,14 +1025,72 @@ public static function setOnlineConsultant(string $value)

public static function setOnlineConsultantScript(string $value)
{
static::setOption(Constants::CRM_ONLINE_CONSULTANT_SCRIPT, $value);
static::setOption(
Constants::CRM_ONLINE_CONSULTANT_SCRIPT,
static::sanitizeOnlineConsultantScript($value)
);
}

public static function setEventTracker(string $value)
{
static::setOption(Constants::CRM_EVENT_TRACKER, $value);
}

private static function sanitizeOnlineConsultantScript(string $value): string
{
$value = trim($value);

if ($value === '') {
return '';
}

$rcct = '';
if (preg_match('/_rcct\s*=\s*[\'"]([A-Za-z0-9_-]+)[\'"]/i', $value, $matches)) {
$rcct = (string) ($matches[1] ?? '');
}

if (!preg_match('~(?:https:)?//[^\s\'"<>]+~i', $value, $matches)) {
return '';
}

$url = (string) $matches[0];
if (strpos($url, '//') === 0) {
$url = 'https:' . $url;
}

$parsedUrl = parse_url($url);
$host = strtolower((string) ($parsedUrl['host'] ?? ''));
$scheme = strtolower((string) ($parsedUrl['scheme'] ?? ''));
$path = (string) ($parsedUrl['path'] ?? '');

$allowedDomains = [
'retailcrm.ru',
'retailcrm.pro',
'retailcrm.es',
'retailcrm.tech',
];

$isAllowedDomain = false;
foreach ($allowedDomains as $domain) {
if ($host === $domain || substr($host, -strlen('.' . $domain)) === '.' . $domain) {
$isAllowedDomain = true;
break;
}
}

if ($scheme !== 'https' || !$isAllowedDomain || $path !== '/widget/loader.js') {
return '';
}

$script = '';

if ($rcct !== '') {
$script .= sprintf('<script>var _rcct = "%s";</script>', htmlspecialcharsbx($rcct));
}

return $script . sprintf('<script async src="%s"></script>', htmlspecialcharsbx($url));
}

public static function setEventTrackerCart(string $value)
{
static::setOption(Constants::CRM_EVENT_TRACKER_CART, $value);
Expand Down
2 changes: 1 addition & 1 deletion intaro.retailcrm/lib/component/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
class Constants
{
public const MODULE_VERSION = '6.8.0';
public const MODULE_VERSION = '6.8.1';
public const CRM_PURCHASE_PRICE_NULL = 'purchasePrice_null';
public const BITRIX_USER_ID_PREFIX = 'bitrixUserId-';
public const CRM_USERS_MAP = 'crm_users_map';
Expand Down
16 changes: 16 additions & 0 deletions intaro.retailcrm/lib/controller/loyalty/basket.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Intaro\RetailCrm\Controller\Loyalty;

use Bitrix\Main\Engine\ActionFilter\Authentication;
use Bitrix\Main\Engine\ActionFilter\Csrf;
use Bitrix\Main\Engine\ActionFilter\HttpMethod;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Request;
use Exception;
Expand Down Expand Up @@ -38,6 +41,19 @@ public function __construct(Request $request = null)
parent::__construct($request);
}

public function configureActions(): array
{
return [
'addLoyaltyToBasket' => [
'prefilters' => [
new Authentication(),
new HttpMethod([HttpMethod::METHOD_POST]),
new Csrf(),
],
],
];
}

/**
* Добавляет данные, полученные при расчете привилегии, в массив корзины
*
Expand Down
5 changes: 5 additions & 0 deletions intaro.retailcrm/lib/controller/loyalty/order.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ private function isOrderCheckIdValid(int $orderId, string $checkId): bool
public function configureActions(): array
{
return [
'loyaltyCalculate' => [
'prefilters' => [
new HttpMethod([HttpMethod::METHOD_POST]),
],
],
'sendVerificationCode' => [
'prefilters' => [
new Authentication(),
Expand Down
28 changes: 28 additions & 0 deletions intaro.retailcrm/lib/controller/loyalty/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ public function __construct(Request $request = null)
public function configureActions(): array
{
return [
'accountCreate' => [
'prefilters' => [
new Authentication(),
new HttpMethod([HttpMethod::METHOD_POST]),
new Csrf(),
],
],
'resendRegisterSms' => [
'prefilters' => [
new Authentication(),
new HttpMethod([HttpMethod::METHOD_POST]),
new Csrf(),
],
],
'activateLpBySms' => [
'prefilters' => [
new Authentication(),
new HttpMethod([HttpMethod::METHOD_POST]),
new Csrf(),
],
],
'activateAccount' => [
'prefilters' => [
new Authentication(),
new HttpMethod([HttpMethod::METHOD_POST]),
new Csrf(),
],
],
'saveUserLpFields' => [
'prefilters' => [
new Authentication(),
Expand Down
Loading
Loading