В первых трёх отчётах нашего цикла прикладных исследований мы детально препарировали системные уязвимости, возникающие при использовании бизнесом самописных интерфейсов и непроверенных облачных надстроек:
- Неконтролируемая утечка данных возникает из-за публичных анонимных эндпоинтов и открытой регистрации аккаунтов, позволяющих выкачивать десятки тысяч персональных карточек клиентов;
- Клиентское массовое присвоение (CWE-915) открывает возможность для любого рядового сотрудника выдать себе права супер-администратора через консоль браузера;
- Компрометация телефонии (CWE-522) происходит при передаче открытых мастер-паролей от АТС и статических ключей TURN-релеев в браузер, что создаёт прямую угрозу международного телефонного фрода;
- Крах финансовой достоверности вызывается лавинообразными триггерами с расходом 622 000 GB-секунд и миллионными кассовыми разрывами из-за отказа от принципа двойной записи.
У всех этих инцидентов одна первопричина: ошибочное предположение о том, что браузер пользователя может участвовать в проверке прав доступа или расчёте бизнес-правил.
При миграции операционной деятельности на промышленное ядро Odoo 19 CE на выделенном сервере под управлением PostgreSQL данная поверхность атаки полностью ликвидируется. Безопасность перестаёт быть декоративной надстройкой на фронтенде и становится детерминированным свойством серверного ядра и базы данных.
В настоящем документе изложен Стандарт Zero Client-Side Trust в 10 пунктах, представляющий собой практическое руководство для технических руководителей по защите корпоративных систем.
Трёхуровневый барьер изоляции Zero-Trust
Ключевое отличие промышленной платформы Odoo 19 CE от архитектур «толстого клиента» (thick-client) заключается в физическом разделении трёх контуров:
Трёхуровневый контур безопасности Zero-Trust в Odoo 19
Недоверенный клиентский периметр
Веб-браузеры, мобильные терминалы и внешние API работают во внешней открытой зоне. Взаимодействие идет строго через шифрованный протокол HTTPS / JSON-RPC.
Шлюз безопасности Odoo 19 ORM (Python)
Каждый сетевой запрос перехватывается сервером приложений. Ядро сверяет таблицы доступа (ACL), внедряет строковые правила (ir.rule) и вырезает закрытые поля.
Изолированная СУБД PostgreSQL
PostgreSQL слушает исключительно локальный сокет 127.0.0.1 или приватный контур VPC. Прямой доступ к порту 5432 из интернета физически невозможен.
Транзакционный контроль ACID
База данных выполняет атомарные транзакции с блокировкой строк (SELECT FOR UPDATE) и проверяет математическое равенство дебета и кредита.
В этой архитектуре браузер является лишь терминалом отображения. Он не имеет сетевого соединения с PostgreSQL, не может выполнять произвольные SQL-команды и не имеет возможности обойти правила проверки прав, заложенные на сервере.
Сравнительная матрица в 10 пунктах
В приведённой ниже таблице сопоставлены типовые дефекты самописных бессерверных систем и механизмы защиты, действующие в промышленном стандарте Odoo 19 dlab.md:
Сравнительная матрица: Уязвимая самописная система против Odoo 19 Hardened
Уязвимый самописный стек (React + NoSQL)
Опасный анти-паттерн- Доступ к нарядам по порядковым номерам без авторизации (BOLA)
- Открытая публичная регистрация для удобства добавления дилеров
- Клиентский оператор spread позволяет перезаписать роль 'admin' (CWE-915)
- Приватные ключи облака и SMS-шлюза доступны в общей коллекции настроек
- Разграничение доступа основано на сокрытии кнопок в меню браузера
- Пароли сотрудников сохраняются открытым текстом в карточках клиентов
- Мастер-пароль sip:admin от всей АТС передается в JSON софтфона
- Грубое сравнение номеров телефонов блокирует повторные заявки
- Реактивные триггеры выкачивают всю базу в память (622 000 GB-s)
- Статусы оплаты на строках ('PAID' против 'paid') и минус на складе
Промышленный стандарт dlab.md Odoo 19 CE
Корпоративный стандарт- Доступ по криптографическому токену UUIDv4 с проверкой const_eq
- Периметр закрыт: регистрация строго по приглашению (auth_signup = b2b)
- Поля защищены через SELF_WRITEABLE_FIELDS; подмена роли вызывает отказ
- Секреты изолированы в ir.config_parameter для base.group_system
- Принудительное внедрение условий WHERE в SQL через правила ir.rule
- Пароли хэшируются с солью по алгоритму PBKDF2-SHA512
- Вызовы шлюзуются через B2BUA, а релеи защищены токенами RFC 5389
- Реляционные связи: существующий клиент открывает новую сделку
- Агрегация данных выполняется СУБД по B-Tree индексам за 2 мс
- Двойная запись (account.move.line) и блокировка складских строк FOR UPDATE
Подробный разбор 10 векторов безопасности
Неавторизованный перебор заказов (CWE-639 / BOLA)
В уязвимом решении данные наряда открывались по порядковому номеру (ORD-202508-0117). В Odoo 19 доступ через публичный портал защищён абстрактным классом portal.mixin. Просмотр заказа анонимным посетителем возможен исключительно при передаче 36-значного криптографического UUID токена access_token. Ядро проверяет токен функцией с постоянным временем выполнения (const_eq), что предотвращает атаки по времени. Попытка перебора номеров SO001, SO002 вызывает ошибку 403 Forbidden.
Периметр публичной саморегистрации (CWE-250)
В заказной системе форму регистрации оставили открытой для всего интернета, чтобы диспетчеры могли регистрировать сотрудников из интерфейса. В Odoo 19 параметр auth_signup.invitation_scope установлен в значение b2b (Регистрация только по приглашению). Сторонний человек не может зарегистрироваться сам и получить доступ к внутренним ресурсам.
Клиентское массовое присвоение (CWE-915)
В исследованной системе запрос updateDoc(userRef, { role: "admin" }) успешно срабатывал из-за отсутствия схемы валидации на сервере. В Odoo 19 права на поля защищены на уровне ORM атрибутом groups="base.group_system". Самостоятельное обновление профиля пользователем фильтруется через белый список SELF_WRITEABLE_FIELDS. Попытка передать привилегированные поля вызывает исключение AccessError.
Утечка секретов через глобальные настройки (CWE-522)
В уязвимой системе ключи сервисных аккаунтов и токены интеграций хранились в открытой для чтения NoSQL-коллекции. В Odoo 19 параметры конфигурации хранятся в защищённой таблице ir.config_parameter, доступ к которой разрешён исключительно группе base.group_system (Супер-администраторы). Рядовые менеджеры и технические специалисты не могут прочитать эти данные ни через интерфейс, ни через API.
Выкачивание данных через фильтрацию только на фронтенде (CWE-284)
В самописной программе ограничение прав заключалось лишь в скрытии пунктов меню, тогда как API отдавал все строки базы. В Odoo 19 безопасность работает на этапе построения SQL-запроса через ir.rule (Row-Level Security). Когда выездной инженер запрашивает задачи, сервер принудительно дописывает в SQL-запрос фильтр WHERE user_id = current_user.id.
Хранение паролей в открытом виде (CWE-256)
В уязвимой системе пароли сохранялись открытым текстом в карточки клиентов для удобства менеджеров. В Odoo 19 пароли никогда не хранятся в открытом виде: ядро использует криптографическое хэширование PBKDF2-SHA512 с уникальной солью, а первичный ввод пароля осуществляется через одноразовую временную ссылку сброса.
Слив мастер-пароля телефонии (CWE-798)
В уязвимом софтфоне логин и пароль от магистрального транка АТС отдавались в браузер каждому сотруднику. В Odoo 19 телефония шлюзуется через выделенный сервер B2BUA. Браузер общается с внутренними номерами с минимальными правами, а передача медиа-трафика использует эфемерные ключи TURN стандарта RFC 5389, сгорающие через 3 600 секунд.
Потеря повторных обращений клиентов
В самописном скрипте поиск дублей по номеру телефона блокировал создание новых заявок от старых клиентов. В Odoo 19 входящий запрос от существующего партнёра (res.partner) автоматически формирует новую коммерческую возможность (crm.lead), сохраняя при этом всю историю прошлых нарядов и установленного оборудования.
Лавины серверных вычислений NoSQL
В документоориентированной базе каждый триггер скачивал десятки тысяч документов в память, расходуя 622 000 GB-секунд в месяц. В Odoo 19 сводные отчёты формируются через оптимизированные B-Tree индексы PostgreSQL за 2 миллисекунды без перерасхода серверных мощностей.
Бухгалтерия на текстовых строках и отрицательный склад
В самописной программе статус оплаты проверялся простым текстом ("PAID" против "paid"), а параллельные списания приводили к отрицательным остаткам. В Odoo 19 все движения средств фиксируются методом двойной записи (account.move.line), а списание складских позиций защищено транзакционными блокировками (SELECT ... FOR UPDATE).
Архитектурные шаблоны и программная реализация защиты
Ниже приведены три реальных примера программного кода, демонстрирующих реализацию стандарта Zero-Trust на уровне XML-правил, моделей ORM Python и конфигурации СУБД PostgreSQL:
Шаблоны серверной защиты Odoo 19 и PostgreSQL
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Изоляция операционных задач по филиалам компании -->
<record id="fsm_task_multi_company_isolation_rule" model="ir.rule">
<field name="name">Изоляция задач по компаниям холдинга</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!-- Ограничение доступа: инженеры видят только назначенные им наряды -->
<record id="fsm_technician_dispatch_personal_rule" model="ir.rule">
<field name="name">Инженеры видят только свои наряды</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[('user_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('project.group_project_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data>
</odoo># -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions, _
class ResPartnerEnterpriseSecurity(models.Model):
_inherit = 'res.partner'
# Финансовые лимиты строго защищены от изменений линейным персоналом
credit_limit = fields.Monetary(
string="Кредитный лимит контрагента",
groups="account.group_account_manager",
tracking=True
)
tax_compliance_status = fields.Selection([
('verified', 'Проверен ФНС'),
('hold', 'Административная блокировка'),
('blacklisted', 'В чёрном списке')
], default='verified', groups="base.group_system", tracking=True)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
# Защита от массового присвоения кредитных лимитов через API
if 'credit_limit' in vals and not self.env.user.has_group('account.group_account_manager'):
raise exceptions.AccessError(_("Нарушение безопасности: Установка кредитных лимитов разрешена только финансовым контролёрам."))
return super().create(vals_list)
def write(self, vals):
if 'tax_compliance_status' in vals and not self.env.user.has_group('base.group_system'):
raise exceptions.AccessError(_("Нарушение безопасности: Изменение налогового статуса доступно только администраторам системы."))
return super().write(vals)-- Регламент ужесточения параметров безопасности PostgreSQL
-- Отзыв опасных публичных прав на схему
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE ON SCHEMA public TO odoo_app_user;
-- Ограничение путей поиска и пула соединений
ALTER USER odoo_app_user SET search_path = public;
ALTER USER odoo_app_user CONNECTION LIMIT 100;
-- Таблица аудита: фиксация критических изменений прав и ролей
CREATE TABLE IF NOT EXISTS audit_security_log (
id SERIAL PRIMARY KEY,
event_timestamp TIMESTAMPTZ DEFAULT NOW(),
db_user VARCHAR(64) DEFAULT CURRENT_USER,
target_table VARCHAR(64),
operation VARCHAR(16),
old_data JSONB,
new_data JSONB
);
CREATE OR REPLACE FUNCTION log_critical_security_event()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_security_log (target_table, operation, old_data, new_data)
VALUES (TG_TABLE_NAME, TG_OP, to_jsonb(OLD), to_jsonb(NEW));
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE OR REPLACE TRIGGER trg_audit_user_groups
AFTER UPDATE ON res_groups_users_rel
FOR EACH ROW EXECUTE FUNCTION log_critical_security_event();Верификация безопасности в промышленном контуре
Для обеспечения непрерывного контроля безопасности администраторы и аудиторы могут использовать следующие скрипты валидации непосредственно в тестовых и продуктовых средах.
Защищённая модель Odoo 19 на Python
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions, _
class EnterpriseSecurityPolicy(models.Model):
_name = 'enterprise.security.policy'
_description = 'Регламент безопасности Zero-Trust'
_check_company_auto = True
name = fields.Char(string="Идентификатор политики", required=True)
company_id = fields.Many2one('res.company', string="Организация", required=True, default=lambda s: s.env.company)
active_mfa_required = fields.Boolean(string="Обязательный аппаратный токен 2FA", default=True)
allowed_cidr = fields.Char(string="Разрешенные подсети CIDR", default="10.0.0.0/8,192.168.1.0/24")
@api.constrains('company_id', 'allowed_cidr')
def _check_security_isolation(self):
for record in self:
if not record.company_id:
raise exceptions.ValidationError(_("Нарушение Zero-Trust: Политика безопасности должна быть жестко привязана к компании."))
def write(self, vals):
if not self.env.user.has_group('base.group_system'):
raise exceptions.AccessError(_("Нарушение Zero-Trust: Изменение политик безопасности доступно только Root-Администраторам."))
return super().write(vals)Триггер аудита прав и безопасности на уровне ядра PostgreSQL
-- Эшелонированная защита PostgreSQL: Неизменяемый аудит эскалации привилегий
CREATE TABLE IF NOT EXISTS sys_security_audit_vault (
audit_id BIGSERIAL PRIMARY KEY,
audit_timestamp TIMESTAMPTZ DEFAULT CLOCK_TIMESTAMP(),
operator_user VARCHAR(64) DEFAULT CURRENT_USER,
action_type VARCHAR(16) NOT NULL,
affected_user_id INT NOT NULL,
granted_group_id INT NOT NULL
);
CREATE OR REPLACE FUNCTION trap_privilege_escalation()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO sys_security_audit_vault (action_type, affected_user_id, granted_group_id)
VALUES (TG_OP, NEW.uid, NEW.gid);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE OR REPLACE TRIGGER trg_vault_user_privileges
AFTER INSERT OR UPDATE ON res_groups_users_rel
FOR EACH ROW
EXECUTE FUNCTION trap_privilege_escalation();Четырёхэтапная методология безопасной миграции
При переводе предприятий с уязвимых самописных порталов на ядро Odoo 19 CE экспертная группа dlab.md реализует миграцию по четкому протоколу:
4-этапный регламент безопасной миграции на Odoo 19
Криминалистический аудит и экстренная изоляция (Дни 1–2)
Находим все открытые эндпоинты и скомпрометированные ключи. Немедленно проводим ротацию паролей телефонии и сервисных ключей облака.
Развертывание защищенного ядра Odoo 19 (Неделя 1)
Разворачиваем выделенный облачный сервер в европейском дата-центре с закрытым портом PostgreSQL и настроенными правилами ir.rule.
Очистка данных и восстановление баланса (Неделя 2)
Очищаем базу от мусорных логов, восстанавливаем дубликаты телефонных номеров и вводим корректные начальные остатки методом двойной записи.
Запуск в эксплуатацию и непрерывный мониторинг
Переключаем рабочий трафик, переводим звонки на безопасный шлюз B2BUA и подключаем систему к контуру мониторинга инцидентов.
Когда модель Zero-Trust избыточна, а когда обязательна
Инженерный подход требует взвешенного сопоставления требований безопасности и накладных расходов на их реализацию.
Границы применимости: Быстрое прототипирование против боевого контура ERP
Быстрое прототипирование (Низкий риск)
Временные среды- Внутренние хакатоны и исследовательские эксперименты
- Одноразовые промо-лендинги со статическим контентом
- Первичное тестирование макетов интерфейса с пользователями
- Изолированные тестовые стенды с синтетическими данными
- Открытые порталы технической документации
Боевой контур ERP Zero-Trust (Промышленный стандарт)
Обязательный стандарт- Системы, обрабатывающие реальные клиентские платежи и счета
- Базы данных с персональными данными (PII) и зарплатными ведомостями
- Телекоммуникационная инфраструктура, подключенная к операторам связи
- Склады с реальными товарно-материальными ценностями
- Контуры, подлежащие аудиту, требованиям GDPR и стандартам ISO 27001
Анализ компромиссов
- Соотношение скорости запуска и эшелонированной обороны показывает, что детальное описание матриц доступа в
ir.model.access.csv, разработка правилir.ruleи разграничение прав на полях требуют дисциплинированного проектирования на ранних этапах. Для временного двухдневного проекта эти затраты избыточны. Для системы, управляющей финансами и складом, отказ от этого уровня является прямой халатностью. - Баланс между временем отклика и безопасностью подтверждает, что проверка прав на сервере занимает минимальное процессорное время. Благодаря тому, что Odoo 19 компилирует правила безопасности в нативные SQL-условия
WHERE, выполняемые индексами PostgreSQL, задержка составляет менее 2 миллисекунд на запрос, полностью исключая атаки с повышением привилегий. - Выбор между кажущимся удобством пользователей и требованиями регуляторов означает, что система без строгого логирования действий может выглядеть проще в повседневной работе. Но при возникновении внутренних разногласий или налоговой проверки отсутствие неизменяемого журнала аудита оставляет руководство компании без юридической доказательной базы.
Чеклист предстартовой проверки ERP для CISO и технических директоров
Перед подписанием акта ввода новой учётной системы в промышленную эксплуатацию служба безопасности обязана верифицировать следующие контрольные пункты:
-
1. Проверьте сетевую изоляцию порта базы данных, убедившись, что PostgreSQL слушает исключительно
127.0.0.1или закрытую подсеть VPC, а доступ к порту 5432 из внешней сети заблокирован. -
2. Включите регистрацию строго по приглашениям, установив параметр
auth_signup.invitation_scopeв значениеb2bи отключив свободную регистрацию аккаунтов. - 3. Проверьте стойкое хэширование паролей через запрос к базе, чтобы убедиться, что пароли пользователей хранятся в виде хэшей PBKDF2-SHA512 с индивидуальной солью.
-
4. Протестируйте защиту системных параметров, попробовав прочитать таблицу
ir.config_parameterиз-под учетной записи рядового сотрудника через JSON-RPC и убедившись, что запрос завершается ошибкойAccessError. -
5. Подтвердите наличие правил фильтрации строк, проверив активность правил
ir.ruleдля ключевых моделей (sale.order,project.task,account.move), исключающих доступ к чужим документам. -
6. Проведите аудит белых списков на конфиденциальных полях, убедившись, что поля финансового лимита, себестоимости и ролей содержат явный атрибут
groups="...". - 7. Убедитесь в изоляции телефонии через B2BUA, проверив, что софтфон общается с сервером B2BUA, а в настройках TURN используются эфемерные ключи RFC 5389.
- 8. Проверьте контроль баланса двойной записи, убедившись, что база данных отменяет транзакции, в которых сумма дебета не равна сумме кредита.
-
9. Верифицируйте блокировки от состояния гонки, проверив, что списание остатков на складе использует блокировки
SELECT ... FOR UPDATE. - 10. Протестируйте неизменяемый журнал аудита, убедившись, что изменение административных групп пользователей фиксируется триггером в изолированной таблице аудита.
Вопросы по аудиту безопасности Odoo 19 и PostgreSQL
Odoo 19 использует движок ir.rule для перехвата запросов до их выполнения. Когда пользователь обращается к записям, ядро вычисляет активные правила доступа и автоматически добавляет их в виде условий WHERE прямо в тело формируемого SQL-запроса к PostgreSQL. Это гарантирует отсечение неавторизованных данных на уровне самой базы данных независимо от переданных из браузера параметров.
Класс portal.mixin обеспечивает безопасный доступ клиентов к своим документам (счетам, заказам) без ввода постоянного логина и пароля. Вместо порядковых номеров формируется случайный криптографический UUID токен access_token. Просмотр возможен только при предъявлении токена, который проверяется алгоритмом постоянного времени const_eq для предотвращения атак по времени.
Развёртывание Odoo 19 Community Edition на выделенной виртуальной машине обеспечивает полный контроль над сетевым периметром, ключами шифрования и настройками безопасности. Это исключает риски совместного использования ресурсов несколькими компаниями и гарантирует соблюдение требований суверенитета данных.
В Odoo 19 изменение атрибутов пользователя фильтруется через список SELF_WRITEABLE_FIELDS, разрешающий менять только безопасные параметры вроде языка или часового пояса. Изменение полей групп безопасности (groups_id) доступно только администраторам (base.group_system), что полностью блокирует атаки класса Mass Assignment (CWE-915).
Итоги и стратегия защиты корпоративного ядра
Информационная безопасность предприятия определяется её архитектурным фундаментом. Использование непроверенных интерфейсов и отказ от серверной модели проверки прав создаёт неприемлемые риски: утечки данных, кражу телефонного трафика и потерю финансового контроля.
Стандарт Zero Client-Side Trust в 10 пунктах обеспечивает надёжную защиту: изоляцию базы данных за сервером приложений, математический контроль двойной записи, использование временных криптографических токенов и строгую фильтрацию каждого запроса на базе Odoo 19 CE и PostgreSQL.
Для проведения независимого аудита безопасности перед внедрением ERP или проектирования защищённой инфраструктуры Odoo 19 обратитесь к архитектурной группе dlab.md.
Готовите стек ИИ и ERP к корпоративному аудиту безопасности?
Наши системные архитекторы проводят фиксированный 10-дневный Zero-Trust аудит архитектуры (OAuth 2.0 CAB, EU AI Act ст. 12/14, GDPR ст. 25, харденинг PostgreSQL и Odoo 19) с готовой дорожной картой устранения уязвимостей.