Version 2.0.0 — security, Yandex SmartCaptcha, PHP 8.5 and refactoring
Attention. This version contains incompatible changes (see the last section). Before updating a live site, create a backup.
1. Vulnerability fixes
Unauthenticated hijacking of another user's order via e-mail. In ajax/submit.php, when USE_CREATE_ORDER=Y, the buyer was searched in UserTable by the EMAIL value from the request, and an order was created for the found user: an anonymous visitor, knowing someone else's email, could place an order in that user's personal account. The order owner is now determined solely by the current session — either an authorized user or an anonymous cart buyer. The payer type, the product for one-click purchase, and the comment field are taken from the signed component parameters, not from the request.
Unauthenticated writing to an arbitrary information block. IBLOCK_ID, SECTION_ID, IBLOCK_ITEM_NAME and ACTIVE were accepted directly from the request, and all POST keys for which a property with that code was found were placed in PROPERTY_VALUES. Server-side form parameter signing has been introduced (LogicloudConnectSecurityFormSigner based on BitrixMainSecuritySignSigner): the browser returns a signed string that cannot be tampered with. The information block is additionally checked for belonging to the current site; properties are accepted only from the list of fields declared in the form settings. The call to htmlspecialchars_decode() for the element name has been removed — it restored HTML tags from entities.
Injection into email via an arbitrary email event template. The event name came from the request, and all POST values were stored in C_FIELDS without cleaning — an attacker could select any registered template on the site and insert newline characters into macros, adding their own email headers. The event name is now taken only from signed parameters; all \r and \n characters are removed from all values, and the email body is escaped.
Unauthenticated invocation of arbitrary core event handlers. The code executed GetModuleEvents("main", $_POST['EVENT_ID'], true) and passed the entire $_POST to the handler, meaning an anonymous client could invoke any handler registered for the main module with arbitrary arguments. The event name is taken from signed parameters, the search scope is limited to the logicloud.connect module, and the handler receives a set of fields filtered by a whitelist.
Unauthenticated file upload to a web-accessible area. All $_FILES elements were iterated (the field name was chosen by the attacker), files were saved to the root /upload/ and /orders/ directories without restrictions on extension, size, or quantity. Now uploads are possible only in "File" type fields declared in the component settings; a whitelist of extensions is enforced, with a limit of 10 MB per file and 10 files per field, is_uploaded_file() validation is applied, files are stored in a separate directory /upload/logicloud.connect.forms/ and are bound to the module.
CSRF and mass assignment on module settings saving. Both entry points — options.php and admin/logicloud.connect.php — wrote ALL incoming POST keys to settings without a whitelist and without sessid verification: a third-party page could overwrite any setting on behalf of an administrator, including captcha keys and the scripts option, which is rendered on every site page without escaping. check_bitrix_sessid() and bitrix_sessid_post() have been added to both forms, a "W" permission check for the module is enforced, and a whitelist of saveable settings is applied.
CSRF on arbitrary file deletion. The endpoint property/image/ajax/imageUpdate.php executed CFile::Delete($_POST['id']) without sessid verification — a third-party page could force the administrator's browser to delete any file from b_file: logos, product images, documents. sessid and permission checks have been added; deletion is allowed only for files uploaded by this module; uploads are restricted by a whitelist of extensions and size.
Unauthenticated write to module settings. The endpoint tools/set.preview.option.php included prolog_before.php, meaning it was accessible anonymously and without sessid: any visitor could infinitely overwrite the setting with an arbitrary structure. The endpoint is only needed for the admin page, so it has been moved to prolog_admin_before.php with sessid and 'W' permission checks; the accepted structure is now strictly validated.
unserialize() without class restrictions. The connect-temp-file-options setting value was read via unserialize() without allowed_classes in options.php, admin/logicloud.connect.php, tools/upload.file.php, and lib/Admin/Options.php, and an arbitrary string could be written to this setting via mass assignment above — meaning that with a suitable gadget chain, this resulted in PHP Object Injection. All reads have been converted to \Logicloud\Connect\Settings::getArray() with allowed_classes = false and type checking.
Unrestricted file upload of settings. tools/upload.file.php saved a file using the field name from the request without sessid, permissions, or an extension whitelist. Checks for sessid and 'W' permissions have been added, only declared file settings are accepted, an extension whitelist is enforced, and file size is limited.
Unauthenticated access to elements of an arbitrary information block. Pages include/ajax/tariff, include/ajax/equipment, and include/ajax/channels passed ELEMENT_ID, IBLOCK_ID, and SECTION_ID directly from the request to components with USE_PERMISSIONS set to 'N', allowing reading of information block content not intended for public display. The tariff information block is accepted only from the solution's information block list, the equipment information block is fixed via a macro, the channels section is checked for belonging to the channels information block, and all identifiers are cast to numbers.
XSS in the JS context of form templates. Values were output inside a single-quoted string literal via JSON.parse(''), while json_encode() does not escape single quotes by default; FUNCTION_JS and the public captcha key were also inserted without escaping. In all eight form templates, the initialization block has been rewritten using CUtil::PhpToJSObject() and CUtil::JSEscape(), and two nearly identical blocks have been merged into one.
XSS via form field values. Hidden fields were printed as input type hidden with name and value attributes using PHP short tags without escaping, and values come from information blocks (e.g., tariff name); a quote in the value would break the attribute. Escaping has been added in all eight form templates, as well as in component.php where the final value and placeholder attributes are assembled.
File creation using a path from component parameters. The AREA_ID parameter was inserted into the path as-is, and sequences of ../ allowed the creation of an empty PHP file at any point within the document_root. The value is now sanitized to allow only characters [A-Za-z0-9_-].
Debug log in the site root. The installation wizard wrote /log.txt to the document_root on every run, and the file remained accessible via HTTP. The logging has been removed.
Additional information from the security check report
The following four groups of issues were received in a separate check report and have been fixed in this version.
1.1. Reflected XSS in the tariff block
The catalog.section.list/tariff.tabs template printed the REGION_ID value inside the data-ajax-region HTML attribute without escaping, and the value came from the public unauthenticated endpoint include/ajax/tariff.tabs/. A quote in the value closed the attribute, and a link of the form /include/ajax/tariff.tabs/?REGION_ID= followed by a greater-than sign and script tag executed arbitrary JavaScript in the context of the site domain. The value is now converted to a number and escaped.
The endpoint include/ajax/tariff.tabs/index.php normalizes all input values before passing them to the component: REGION_ID and ACTIVE_TAB are converted to integers, SUBURBAN to a fixed set of true/false. The template no longer relies on type casting being performed by someone else: it repeats the normalization itself, as it may be overridden separately.
The comparison of the active tab has been converted to numeric: after casting ACTIVE_TAB to int, strict comparison with a string section ID would no longer work.
In the same template, the remaining output is escaped: CODE, NAME, and section IDs.
1.2. Output of infoblock values without escaping
The template news.detail/tariff.modal output NAME, PREVIEW_TEXT, and property values INET_EXTRA, INET_SPEED, MAIN_OPTION (including DESCRIPTION), PRICE, PRICE_MARK, and OLD_PRICE directly into the markup. All output has been converted to htmlspecialcharsbx().
The call to html_entity_decode() for the MAIN_OPTION value has been removed: it returned saved HTML entities as real tags, thus undoing the escaping performed when saving the element.
1.3. Links and social media icons
The template news.list/social printed the link address without scheme validation: a value like javascript:... was executed when clicking the icon. Now only http, https, mailto, tel schemes and relative addresses are allowed, the value is escaped, and the link is given rel="noopener noreferrer".
The icon file content was inserted into the markup via file_get_contents() as-is: SVG with a script or event handler was executed inline on the page. A sanitizer \Logicloud\Connect\Security\Svg has been added: it parses SVG and keeps only graphic tags and attributes from a whitelist, discarding script, foreignObject, event handlers, links with javascript:/data:, and DOCTYPE with external entities (XXE). Inline insertion is preserved, so icon recoloring via CSS continues to work; raster icons and SVG that did not pass parsing are output via an img tag.
Elements without a link or without an icon are skipped, not output as empty.
1.4. Configuration of Additional Scripts and Counters
The setting value scripts was output in footer.php via htmlspecialcharsback() — this function does not escape but removes previously applied escaping, so the script tag saved in the setting was executed for all visitors on every website page.
Value normalization has been added: \Logicloud\Connect\Settings::sanitizeScripts() retains only external counter connections — script tags with a src attribute without inline body, img tags, and noscript tags containing such img elements. Attributes are filtered by a whitelist; event handlers and values containing javascript:/data:/expression() are discarded; the source must be an absolute https address.
Normalization is performed twice: when saving the settings form (the value is already cleaned upon writing, and the administrator sees a notification if something was filtered) and when outputting in the template — so that values saved by previous versions are also not executed.
A hint with the allowed format is displayed below the setting field.
2. Transition from Google reCAPTCHA v3 to Yandex SmartCaptcha
Form validation has been switched to Yandex SmartCaptcha. The token is verified on the server at the specified address; the result is read from the status field of the response (ok/failed).
Setting fields have been replaced: instead of Public Key, Secret Key, and Minimum User Score, there are now Client Key and Server Key. The score threshold has been removed — SmartCaptcha returns a binary response and has no score field. The link for keys leads to the Yandex Cloud console.
Keys are stored in module settings under the names captcha-client-key and captcha-server-key. Inapplicable Google keys are automatically deleted upon update.
The captcha is invisible: the widget is rendered in a hidden form container and triggered upon submission; the visitor sees the verification only in disputed cases.
The intermediate request to ajax/captcha.php has been removed, as this is a service requirement: the SmartCaptcha token is single-use and valid for five minutes, so a preliminary check would consume it, causing form submission to always fail. The token is sent directly to ajax/submit.php. The file ajax/captcha.php remains and returns code 410 so that visitors' cached old scripts receive a clear response instead of 404.
Widget errors (network-error, javascript-error) have been handled and a timeout safeguard added; after submission, the widget resets and resubmission works without page reload.
Previously, the captcha did not work at all: the Google script was not connected to the website pages, so the call to grecaptcha in forms.js ended with an error. Now, the widget loader has been added to the header.php template and is connected only when the keys are filled.
3. Support for PHP 8.5
Short opening tags have been removed from lib/Admin/Options.php and two language files; when short_open_tag was set to Off, the code was displayed as text.
Variables $by/$order passed by reference to CEventMessage::GetList(), CLanguage::GetList(), and CSite::GetList() in six files have been initialized; previously, 'Undefined variable' errors were issued.
Class properties of the installer are declared explicitly instead of using var and dynamic properties (deprecated since PHP 8.2); MODULE_GROUP_RIGHTS, PARTNER_NAME, and PARTNER_URI have been added.
Access to non-existent array keys via ?? and isset in settings, templates, and handlers has been eliminated.
A check for the minimum PHP version with a clear message has been added to the installer.
4. Caching
Cache lifetime reduced from 36000000 seconds (more than 400 days) to reasonable values in fifteen component calls: with managed cache disabled, content was not updated for over a year.
5. Refactoring
Added module class layers: \Logicloud\Connect\Settings (whitelist and secure reading of settings), \Logicloud\Connect\Security\FormSigner (form parameter signing), \Logicloud\Connect\Security\Captcha (server-side SmartCaptcha verification), \Logicloud\Connect\Iblock\Resolver (information block search and property whitelist).
Explicit autoloading registration was added to include.php. Until version 2.0.0, the file was empty, and loading classes from lib/ entirely relied on the core's automatic rule, which converts paths to lowercase. On case-sensitive file systems, files lib/Main.php and lib/Admin/Options.php were not found by this rule.
Settings saving logic was moved to admin/settings_handler.php: options.php and admin/logicloud.connect.php contained two copies of the same code. The tab switcher was moved to admin/include/tabs.php.
The form component was rewritten: signed parameters instead of open POST, escaping of ready-made attributes, a unified error message map, and numeric field sorting instead of strcmp.
Public endpoints were standardized: request method verification, clear response codes (400/403/405/413/415/500) instead of silent exit.
Form templates: eight nearly identical JS initialization blocks were consolidated into one, and method and enctype attributes, which were missing from the form, were added.
6. Incompatible changes
After the update, the form is protected by captcha only when SmartCaptcha keys are filled. Google keys are not migrated and are deleted. Obtain client and server keys from Yandex Cloud SmartCaptcha and specify them in the module settings.
The user form event is now searched among handlers registered for the logicloud.connect module, not main. Existing handlers must be re-registered: AddEventHandler('logicloud.connect', 'YOUR_EVENT', ...). The handler receives an array containing only fields declared in the component settings.
The public endpoint ajax/submit.php accepts only requests with a correct parameter signature, valid for one day, and only for the site where it was issued. Direct calls from external code that manually constructed POST requests will stop working. If the page containing the form is served from a long-lived cache, ensure that the cache lifetime is less than one day.
The endpoint ajax/captcha.php no longer performs verification and responds with status code 410.
The endpoint tools/set.preview.option.php requires authorization in the admin panel and the 'W' permission for the module.
The module settings page requires the 'W' permission for the logicloud.connect module. After the update, verify the permissions for user groups.
Changes in the site template and public pages (catalog install/wizards) are placed in /bitrix/wizards and are applied by the wizard during installation. For an already deployed site, you must manually transfer the following files, taking your modifications into account: include/ajax/tariff/index.php, include/ajax/equipment/index.php, include/ajax/channels/index.php, include/ajax/tariff.tabs/index.php, templates/logicloud.connect/header.php, templates/logicloud.connect/footer.php, and three component templates: catalog.section.list/tariff.tabs, news.detail/tariff.modal, news.list/social.
Inline code in the 'Additional scripts and counters' field is no longer displayed. The classic Yandex.Metrica snippet written directly in the field will not work after the update; use an external file connection and a pixel in the noscript tag. The saved value is not overwritten during the update: you will see it in the field and will consciously transfer the counters.
7. Update installation
A fatal error 'syntax error, unexpected token opening angle bracket, expecting end of file' in include.php on the first line has been fixed. This prevented the module from connecting after installation, and the 'Installed Solutions' page in the admin panel also crashed. Cause: when publishing to the Marketplace, include.php is wrapped with protection—the file code is embedded in the middle of the wrapper with the opening tag removed, and a license check block is appended at the end, which starts with an opening tag. The file version 2.0.0 was formatted in a modern style without a closing tag, so the opening tag of the license check block ended up inside the PHP code. Now include.php ends with a closing tag, as required by the wrapper.
The use operator and single-line comments have been removed from include.php: the wrapper minifies the code into a single line, and in this form both constructs are unreliable. Classes are specified using fully qualified names. Requirements for file formatting are described in a comment within the file itself, so they are not lost during subsequent edits.