În ultimii trei ani, un anti-pattern arhitectural riscant a câștigat teren în rândul companiilor mijlocii din Europa Centrală și de Est (inclusiv Republica Moldova și România). Presate de costurile recurente ridicate ale licențelor software de tip SaaS tradițional și de rigiditatea fluxurilor standard, echipele executive apelează tot mai frecvent la agenții de dezvoltare rapidă pentru construirea de sisteme „CRM și ERP la cheie” într-un interval nerealist de șase până la opt săptămâni.
La prima vedere, livrabilul prezentat acționarilor arată impecabil. Construit pe framework-uri moderne de frontend precum React, Next.js sau Vite, sistemul oferă o interfață rapidă, tranziții fluide, panouri de dispecerat de tip kanban, temă întunecată și sincronizare nativă cu canale de mesagerie instantă. Managementul este adesea încântat de viteza cu care prototipul a devenit operațional, fără a necesita lunile de planificare asociate sistemelor de nivel enterprise.
Cu toate acestea, atunci când echipa de inginerie criminalistică de la dlab.md efectuează auditul codului-sursă și al infrastructurii înainte de migrarea către un nucleu industrial, constatăm cu regularitate o realitate îngrijorătoare: aplicația nu dispune de un backend server real.
În loc să canalizeze tranzacțiile financiare și operațiunile critice printr-un strat securizat de logică de afaceri, furnizorii de soluții rapide transferă întreaga povară a validării datelor, a calculului prețurilor și a controlului accesului direct în pachetele JavaScript descărcate în browserul utilizatorului. Baza de date cloud (Cloud Firestore sau instanțe NoSQL similare) este expusă direct către internetul public, fiind protejată exclusiv prin comutatoare vizuale din interfață și reguli declarative superficiale.
Într-un audit tehnic recent realizat asupra unei platforme comerciale ce gestiona 42.825 de fișe de clienți, 2.909 comenzi de vânzare și o rețea de 55 de tehnicieni de teren, orice observator extern neautentificat se afla la o distanță de exact două cereri HTTP standard de exfiltrarea completă a bazei de date și preluarea rolului de super-administrator.
Acest raport tehnic detaliază mecanismele de eșec ale arhitecturilor serverless de tip „thick-client” (client îngroșat), analizează vectorii de atac asociați atribuirii necontrolate pe client (CWE-915) și autorizării deficitare la nivel de obiect (OWASP API1:2023 / CWE-639) și demonstrează modul în care nucleul Odoo 19 CE și PostgreSQL impun un standard inviolabil de tip Zero Client-Side Trust.
Constatări criminalistice: arhitectură serverless thick-client
Date extrase în timpul auditului de securitate pe un eșantion de 42.825 de înregistrări
Anatomia anti-pattern-ului „serverless thick-client”
În ingineria sistemelor enterprise, separarea strictă între prezentare, logica operațională și persistența datelor este fundamentală. O arhitectură industrială funcționează pe principiul Zero Client-Side Trust (Încredere Zero pe Partea de Client): browserul web sau aplicația mobilă este considerată un terminal inerent nesigur și ostil. Browserul are doar responsabilitatea de a rula interfața grafică și de a colecta datele introduse de operator. Acesta nu deține acces direct la protocolul bazei de date, nu cunoaște constrângerile de integritate referențială și nu poate decide unilateral dacă o tranzacție financiară este validă.
În arhitectura de tip „thick-client”, acest principiu este abandonat complet. Pentru a grăbi livrarea și a elimina costurile de scriere a unui API REST/JSON-RPC robust cu mașini de stare tranzacționale, dezvoltatorii conectează componentele React direct la baza de date cloud prin intermediul SDK-urilor de client.
Comparație arhitecturală: client îngroșat vs. nucleu industrial Odoo 19
Serverless Thick-Client (React + bază NoSQL directă)
Anti-pattern vulnerabil- Structura bazei de date și colecțiile expuse în fișierele JavaScript publice
- Autorizarea accesului bazată pe verificări vizuale (if user.role === 'admin')
- Operațiuni directe de scriere din browser (updateDoc, setDoc, mutații REST)
- Vulnerabilitate critică la atribuire în masă (CWE-915) și enumerare BOLA (CWE-639)
- Mutații documentare NoSQL fără suport ACID ce produc rupturi de bilanț
Odoo 19 CE + PostgreSQL (standard Zero-Trust)
Standard enterprise- Zero acces direct la baza de date; PostgreSQL izolat pe interfața locală 127.0.0.1
- Filtrare triplă server-side: ir.model.access, ir.rule și grupuri de câmp
- Tranzacții financiare atomice (ACID) cu blocare exclusivă la nivel de rând (FOR UPDATE)
- Jetoane criptografice unice (UUIDv4) cu verificare în timp constant (const_eq)
- Trunchiere automată a metadatelor sensibile înainte de serializarea răspunsului RPC
Când o organizație elimină serverul de aplicații intermediar în favoarea apelurilor directe din client, perimetrul de protecție dispare. Am documentat această prăbușire operațională de-a lungul a patru etape succesive în cursul auditului nostru criminalistic.
Descoperirea neautentificată a schemei și enumerarea BOLA (CWE-540 & CWE-639)
Atunci când un vizitator accesează pagina publică de autentificare (/login) a unei aplicații de tip thick-client, serverul web livrează întregul pachet de fișiere JavaScript compilate. Din cauza configurărilor standard din instrumentele de împachetare (Vite sau Webpack) care adună toate rutele într-un număr minim de fișiere fără separare pe permisiuni, clientul primește întregul cod al panoului de administrare înainte de introducerea vreunui nume de utilizator sau parole.
Examinarea fișierului principal prin instrumentele de dezvoltare ale browserului (DevTools) a scos la iveală întregul inventar de module interne:
- Modulul
PayrollPage.jsincludea algoritmii de calcul al salariilor, penalizărilor, bonusurilor și comisioanelor de vânzări. - Fișierele
FinancesPage.jsșifinance.service.jsconțineau planul de conturi interne, registrele de casă, cheile furnizorilor de plăți și procedurile de reconciliere. - Componenta
SettingsPage.jsexpunea configurările infrastructurii, adresele de webhook și credențialele de acces la telefonia cloud.
Deoarece interogările NoSQL erau construite direct în serviciile de frontend, un observator extern a reconstituit harta completă a celor 36 de colecții ale bazei de date în mai puțin de 60 de secunde.
În paralel, aplicația oferea un punct final public pentru verificarea stadiului comenzilor de către cumpărători: https://app.client-domain.com/getOrder?orderNumber=ORD-202508-0117.
Acest punct final suferea de o vulnerabilitate clasică de tip Broken Object-Level Authorization (OWASP API1:2023 / CWE-639). Funcția serverless nu verifica identitatea apelantului și nici dacă acesta era titularul legitim al comenzii solicitate. Deoarece numerele de comandă erau generate secvențial, un script bash simplu a permis enumerarea incrementală a fiecărei tranzacții din baza de date. În câteva zeci de minute, întregul istoric de 42.825 de clienți (cu nume, prenume, adrese de livrare, telefoane și sume achitate) a putut fi descărcat integral.
Înregistrarea publică a identităților și neutralizarea filtrelor (CWE-250)
Adesea, susținătorii arhitecturilor serverless susțin că regulile de securitate declarative (allow read, write: if request.auth != null;) sunt suficiente pentru a bloca accesul neautorizat. Premisa este că persoanele din exterior nu dețin conturi valide, deci nu pot interoga baza de date.
În realitate, această barieră s-a prăbușit din cauza unei decizii de conveniență luate în faza de dezvoltare.
Pentru a permite dispecerilor să adauge rapid noi colaboratori și tehnicieni de teren direct din interfață, programatorii au introdus un formular de creare de cont. În loc să implementeze această acțiune pe server printr-o funcție securizată bazată pe pachetul de administrare (firebase-admin), dezvoltatorul a apelat direct metoda client:
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
export async function registerFieldTechnician(email, temporaryPassword) {
const auth = getAuth();
// Apel direct din browser care impune menținerea deschisă a înregistrării publice
const userCredential = await createUserWithEmailAndPassword(auth, email, temporaryPassword);
return userCredential.user;
}Pentru ca acest cod să funcționeze din browserul oricărui dispecer, în consola Google Cloud Identity Toolkit a fost necesară activarea permanentă a înregistrării publice de conturi (accounts:signUp).
Drept consecință tehnică, orice persoană din exterior putea extrage cheia publică a proiectului (apiKey) din codul paginii, trimitea o cerere standard HTTP POST către serviciul de autentificare și obținea un jeton criptografic perfect valid în 200 de milisecunde. Clauza request.auth != null a fost instantaneu satisfăcută. Granița dintre un atacator anonim și un utilizator autentificat a încetat să mai existe.
Depozitul de secrete din colecția settings/* (CWE-522)
Odată obținut un jeton de utilizator obișnuit, ce informații pot fi extrase?
Dezvoltatorii de frontend tind să creadă că dacă ascund butonul „Setări” în interfața React ({currentUser.role === 'admin' && <AdminSettingsMenu />}), un utilizator obișnuit nu va putea accesa acele resurse.
Însă la nivelul bazei de date Firestore, colecția settings avea definită o regulă globală permisivă: allow read: if request.auth != null;.
Interogarea directă a colecției din consola browserului a scos la lumină secrete operaționale majore:
- Cheia privată RSA a contului de serviciu Google Cloud (
-----BEGIN PRIVATE KEY-----) asigura privilegii depline de editare asupra întregului ecosistem cloud al companiei, permițând modificarea bazelor de date analitice BigQuery și a fișierelor din Cloud Storage. - Cheia de autentificare a gateway-ului de mesagerie SMS permitea expedierea de mesaje frauduloase sub identitatea alfanumerică oficială a companiei pe cheltuiala acesteia.
- Imaginile de înaltă rezoluție ale ștampilei oficiale și ale semnăturii directorului financiar erau salvate pe adrese publice permanente în Cloud Storage, facilitând falsificarea documentelor contabile.
- Parolele în clar ale celor 55 de angajați din teren (
installerPassword) erau stocate direct în fișele de comandă pentru a fi afișate la nevoie în interfață.
Atribuirea în masă pe client și autoproclamarea ca SuperAdmin (CWE-915)
Vulnerabilitatea finală care a desăvârșit compromiterea sistemului este un caz clasic de Client-Side Mass Assignment (CWE-915).
În serviciul de actualizare a profilului utilizatorului (user.service.ts), modificările erau trimise utilizând operatorul spread (...payload) fără filtrare pe lista albă de câmpuri permise:
// Model vulnerabil de scriere din browser
async function updateUserProfile(userId, clientPayload) {
const userDocRef = doc(databaseInstance, "users", userId);
// Întregul conținut furnizat de client este inserat direct în document
await updateDoc(userDocRef, {
...clientPayload,
updatedAt: serverTimestamp()
});
}Deoarece verificarea rolului de administrator era realizată doar în logica de afișare din React, iar regulile bazei de date permiteau posesorului contului să modifice propriul document (allow write: if request.auth.uid == userId;), baza de date a acceptat orice proprietate transmisă în corpul cererii.
Atacatorul a executat o comandă directă în consola browserului:
// Payload de escaladare a privilegiilor executat direct în consolă
const auth = getAuth();
const currentUid = auth.currentUser.uid;
await updateDoc(doc(db, "users", currentUid), {
role: "super_admin",
isVerified: true,
accessLevel: 100,
permissions: ["ALL_PRIVILEGES"]
});În 200 de milisecunde, documentul din baza de date a fost suprascris. La reîncărcarea paginii, routerul React a identificat rolul de super_admin, a afișat panoul de control complet și a acordat permisiuni absolute de vizualizare, modificare și ștergere asupra tuturor datelor organizației.
Securitate comparată: mutație în client vs. model ORM Odoo 19
// Nesigur: Mutație directă pe client cu operator spread nefiltrat
import { doc, updateDoc, serverTimestamp } from "firebase/firestore";
export async function updateEmployeeRecord(targetId, userSubmittedData) {
// Orice atribut trimis de utilizator (ex: role: 'admin', salary: 99999)
// se scrie direct în documentul de producție fără validare backend.
const recordRef = doc(db, "employees", targetId);
await updateDoc(recordRef, {
...userSubmittedData,
updatedAt: serverTimestamp()
});
}# Securizat: Validare strictă pe server, grupuri de câmpuri și blocare la scriere
from odoo import models, fields, api, exceptions, _
class ResUsersCustom(models.Model):
_inherit = 'res.users'
# Rolul de securitate este accesibil exclusiv Administratorilor de Sistem
security_role = fields.Selection([
('technician', 'Tehnician Teren'),
('dispatcher', 'Dispecer Operațiuni'),
('manager', 'Manager Punct Lucru'),
('admin', 'Administrator Sistem')
], string="Nivel Autorizare", default='technician',
groups="base.group_system", tracking=True)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'security_role' in vals and not self.env.user.has_group('base.group_system'):
raise exceptions.AccessError(_("Rolurile de securitate pot fi atribuite doar de Administratori."))
return super().create(vals_list)
def write(self, vals):
if 'security_role' in vals and not self.env.user.has_group('base.group_system'):
raise exceptions.AccessError(_("Modificarea nivelului de securitate necesită drepturi depline."))
return super().write(vals)Arhitectura Zero Client-Side Trust în Odoo 19 și PostgreSQL
Pentru a elimina definitiv breșele de tip CWE-915, CWE-540 și BOLA, arhitectura software trebuie să impună mecanisme de control direct la nivelul nucleului bazei de date și al serverului de aplicații. În Odoo 19 CE susținut de motorul PostgreSQL, securitatea este organizată ca o succesiune de filtre server obligatorii.
Barierele de securitate server-side în Odoo 19
Izolare la nivel de rețea
PostgreSQL ascultă strict pe interfața locală 127.0.0.1 sau într-o rețea privată VPC. Browserul client nu poate iniția conexiuni de rețea directe către baza de date.
Autorizare pe modele (ir.model.access)
Verifică drepturile grupului pe tabelă (Citire, Scriere, Creare, Ștergere) înainte de generarea arborelui de interogare SQL.
Securitate la nivel de rând (ir.rule)
Nucleul interceptează interogarea și injectează clauze SQL WHERE obligatorii, limitând tehnicienii doar la comenzile proprii sau la compania curentă.
Protecție pe câmpuri și trunchiere date
Câmpurile marcate cu groups='base.group_system' sunt eliminate din răspunsul serverului înainte de serializarea datelor pe rețea.
Controlul accesului pe modele (ir.model.access.csv)
În Odoo, niciun model nu poate fi citit sau modificat prin apeluri JSON-RPC fără permisiuni explicite în matricea de securitate. Dacă un operator neautorizat încearcă să apeleze funcția execute_kw pe un model sensibil precum account.move sau hr.payslip, interpretorul Python blochează solicitarea cu o eroare AccessError înainte ca vreo tranzacție SQL să înceapă.
Injectarea forțată a clauzelor SQL (ir.rule)
Când un tehnician de teren solicită lista sarcinilor sale prin interfață, clientul web nu transmite un parametru ușor de manipulat de tipul where technician_id = 42.
În schimb, ORM-ul Odoo interceptează cererea pe server și adaugă automat constrângerile definite în regulile de securitate ale modelului:
<record id="fsm_technician_task_rule" model="ir.rule">
<field name="name">Tehnicienii văd doar sarcinile alocate lor</field>
<field name="model_id" ref="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>Chiar dacă un utilizator trimite un parametru de căutare gol (domain: []) pentru a descărca toate înregistrările, serverul Odoo adaugă la interogare clauza SQL obligatorie: WHERE project_task.id IN (SELECT task_id FROM project_task_user_rel WHERE user_id = current_uid). Izolarea rămâne garantată matematic.
Protecția nativă împotriva atribuirii în masă
Modelele Odoo declară explicit câmpurile modificabile. În Odoo 19, mecanismul SELF_WRITEABLE_FIELDS limitează strict atributele pe care utilizatorii și le pot schimba singuri (cum ar fi limba sau fusul orar). Orice încercare de a transmite câmpuri legate de permisiuni (groups_id) sau apartenență la companie (company_id) declanșează o excepție imediată.
Pentru o apărare în profunzime, iată o procedură stocată PostgreSQL și un declanșator (trigger) care previn modificarea neautorizată a rolurilor direct în baza de date:
-- Apărare în profunzime PostgreSQL: Prevenirea escaladării neautorizate a rolurilor
CREATE OR REPLACE FUNCTION verify_user_privilege_escalation()
RETURNS TRIGGER AS $$
BEGIN
-- Verifică modificarea rolului de securitate și impune permisiuni de administrator
IF NEW.security_role IS DISTINCT FROM OLD.security_role THEN
IF CURRENT_USER != 'odoo_master_admin' AND
NOT EXISTS (
SELECT 1 FROM res_groups_users_rel r
JOIN res_groups g ON r.gid = g.id
WHERE r.uid = (SELECT id FROM res_users WHERE login = CURRENT_USER)
AND g.category_id = (SELECT id FROM ir_module_category WHERE name = 'Administration')
) THEN
RAISE EXCEPTION 'ALERTĂ SECURITATE: Tentativă neautorizată de modificare a rolului pentru utilizatorul % de la % la %',
OLD.id, OLD.security_role, NEW.security_role
USING ERRCODE = '28000';
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE OR REPLACE TRIGGER trg_protect_security_roles
BEFORE UPDATE ON res_users
FOR EACH ROW
EXECUTE FUNCTION verify_user_privilege_escalation();Când nu trebuie utilizată arhitectura serverless și ce compromisuri presupune
Pentru a menține o judecată inginerească obiectivă, arhitecții software trebuie să discearnă scenariile în care abordarea serverless este legitimă de cele în care aceasta devine un risc existențial pentru afacere.
Matrice de adecvare a soluției: serverless vs. ERP relațional
Acceptabil pentru Serverless Thick-Client
Risc scăzut- Site-uri promoționale cu cataloage de produse exclusiv în citire
- Prototipuri demonstrative de scurtă durată șterse după testare
- Aplicații de colaborare vizuală fără modele financiare complexe
- Instrumente de productivitate individuală fără cerințe de multi-tenancy
- Baze de cunoștințe publice și portaluri cu documentație statică
Obligatoriu pentru ERP relațional (Odoo 19 / PostgreSQL)
Grad enterprise- Contabilitate financiară cu balanță în partidă dublă (ACID)
- Gestiune a stocurilor în mai multe depozite cu trasabilitate pe serii
- Dispecerat pentru servicii de teren cu date PII și calcul salarial
- Portaluri B2B cu plăți online, facturare electronică și contracte
- Conformitate strictă cu reglementările GDPR, ISO 27001 sau SOC 2
Analiza compromisurilor tehnice
- Comparând viteza de livrare cu costul total de deținere (TCO), observăm că lansarea unui prototip în React și Firebase poate dura câteva săptămâni deoarece dezvoltatorii omit crearea structurilor relaționale și a mașinilor de stare pe backend. Cu toate acestea, datoria tehnică acumulată explodează rapid. Atunci când compania are nevoie de inventar în timp real, balanță contabilă și audit riguros, adaptarea a sute de interogări din frontend devine imposibilă din punct de vedere economic.
- Analizând complexitatea regulilor declarative față de logica procedurală, regulile declarative din bazele NoSQL devin extrem de greoaie atunci când trebuie să valideze relații complexe între mai multe tabele sau ierarhii extinse de permisiuni. Încercarea de a menține reguli de securitate pentru grupuri dinamice în sute de linii de JSON duce la erori greu de depistat și la depășirea limitelor de evaluare ale furnizorului de cloud.
- Din perspectiva predictibilității costurilor de infrastructură, bazele NoSQL facturează fiecare operațiune de citire și scriere a documentelor. Într-un sistem de gestiune operațională, recalcularea rapoartelor agregate din interfață provoacă milioane de citiri neoptimizate, transformând o factură cloud estimată la 50 de euro într-una de câteva mii de euro pe lună.
Ghidul de audit în 5 minute pentru directori tehnici (CTO) și CISO
Dacă organizația dumneavoastră operează un sistem web dezvoltat pe comandă sau intenționează să achiziționeze o astfel de soluție, aplicați acest protocol de verificare preliminară înainte de a introduce date sensibile în producție:
- Analizați conexiunile de rețea pentru apeluri directe către baza de date deschizând consola de dezvoltare a browserului (
F12), navigând la tab-ulNetworkși filtrând după conexiuniWSsauXHR. Dacă observați conexiuni permanente către domenii precumfirestore.googleapis.com,supabase.cosau cereri REST directe de scriere în colecții (/documents/users/...), aplicația utilizează un model nesigur de tip client îngroșat. - Inspectați pachetele JavaScript publice căutând în fișierele
.jsdescărcate termeni precumpayroll,invoice,settingssauprivate_key. Dacă logica și rutele de administrare sunt prezente în codul descărcat de utilizatorii neautentificați, sistemul încalcă principiul privilegiilor minime (CWE-540). - Verificați mecanismul de înregistrare a conturilor încercând să înregistrați un cont nou cu o adresă de email oarecare prin intermediul punctului final de autentificare, fără a deține o invitație oficială de la administrator. Dacă serviciul returnează o sesiune activă, verificați dacă noile identități pot citi colecțiile interne ale companiei.
- Testați manipularea parametrilor (CWE-915) interceptând o solicitare obișnuită de actualizare a profilului (de exemplu, modificarea numărului de telefon) și injectând parametri administrativi:
{"role": "admin", "is_admin": true, "access_tier": "root"}. Dacă baza de date persistă aceste valori fără erori de validare pe server, întregul model de securitate este compromis.
Recomandări strategice pentru securizarea infrastructurii ERP
Înțelegerea cauzei principale a vulnerabilității CWE-915 (atribuirea în masă pe client) și a breșelor BOLA explică de ce regulile de securitate NoSQL nu pot proteja pe deplin un sistem de gestiune comercială. CWE-915 apare atunci când aplicația acceptă date introduse de utilizator și le leagă direct de proprietățile tabelelor interne fără o listă albă strictă de câmpuri autorizate, bazându-se doar pe restricții vizuale în interfață. Deși regulile declarative pot verifica autentificarea de bază, ele nu oferă suport pentru tranzacții relaționale de tip ACID, uniri complexe între tabele multiple și trunchiere automată a datelor pe server. În schimb, Odoo 19 elimină riscurile BOLA prin clasa portal.mixin și motorul de reguli ir.rule, condiționând accesul neautentificat de un jeton criptografic UUIDv4 de 36 de caractere evaluat în timp constant (const_eq) și filtrând automat interogările autentificate direct în SQL.
Pentru remedierea unei platforme thick-client existente, măsura urgentă constă în blocarea scrierilor directe din browser, dezactivarea înregistrării publice de conturi în serviciul de identitate și redirecționarea operațiunilor de scriere prin funcții server-side securizate. Pe termen lung, strategia optimă este migrarea proceselor operaționale către o platformă enterprise consolidată precum Odoo 19 CE pe infrastructură dedicată cu PostgreSQL, unde principiul Zero Client-Side Trust, integritatea financiară și conformitatea GDPR sunt asigurate nativ în spatele unor bariere server multi-nivel.
Pentru o evaluare tehnică a perimetrului actual al sistemelor dumneavoastră sau pentru planificarea unei migrări arhitecturale către Odoo 19, contactați echipa de inginerie și criminalistică de la dlab.md.
Pregătiți arhitectura AI și ERP pentru un audit de securitate enterprise?
Arhitecții noștri de sistem realizează audituri Zero-Trust cu scop fix în 10 zile (OAuth 2.0 CAB, EU AI Act Art. 12/14, GDPR Art. 25, securizare PostgreSQL și Odoo 19), livrând un plan complet de remediere.