Această pagină e pentru cine ia decizia, nu pentru echipa tehnică. Fără jargon.
Am analizat codul aplicației de rezervări și comenzi a TERASA VERDE și am identificat 10 probleme reale de securitate, dintre care două critice care necesită acțiune în 24 de ore. Aplicația funcționează corect din punct de vedere al utilizatorului, dar are lacune tipice unei dezvoltări rapide: date personale accesibile între conturi, chei de producție expuse și o cale de acces neautorizat la baza de date.
| # | Problema | De ce e urgent |
|---|---|---|
| Critic | Comenzile altor clienți sunt vizibile (COX-01) | Orice client logat poate citi comenzile oricui schimbând un număr în adresă. Date personale expuse - problemă GDPR imediată. |
| Critic | Chei de producție în cod (COX-02) | Parola bazei de date și cheia de plăți live Stripe sunt urcate în repository. Sunt compromise din acest moment; trebuie rotite azi. |
| Ridicat | Cale de injecție în baza de date (COX-03) | Câmpul de căutare permite manipularea directă a interogărilor SQL. Poate expune toată baza. |
Problemele nu sunt semne de neglijență, ci de viteză: cod scris să funcționeze repede, fără trecerea de securitate. Toate cele 10 sunt reparabile, majoritatea în ore, nu zile. Estimare de remediere: ~3-5 zile de lucru pentru echipa voastră, împărțite pe urgențe în Secțiunea 4. După remediere și o reverificare, aplicația ajunge la un nivel de securitate rezonabil pentru producție.
Ce înseamnă „34 zgomot triat"
Uneltele automate au ridicat 47 de alerte. Le-am verificat manual pe toate. 34 au fost false pozitive (tipare care par probleme dar nu sunt). Raportez doar cele 10 reale. Trierea asta e diferența dintre un raport pe care îl poți acționa și o listă de 300 de avertismente pe care nu o citește nimeni. Un exemplu de fals pozitiv, cu explicație, în Anexă.
Analiză statică asistată de unelte, urmată de verificare manuală a fiecărei constatări. Uneltele găsesc tipare; judecata decide ce e real, cât de grav e și cum se repară în contextul acestei aplicații.
| Unealtă | Rol |
|---|---|
| Semgrep CE | Analiză statică, tipare de vulnerabilități (autorizare, injecție, cripto) |
| gitleaks | Secrete în cod și în istoricul git |
| Trivy | Vulnerabilități cunoscute în dependențe (CVE) |
| Verificare manuală | Trierea rezultatelor, logica de business, contextul |
Constatările sunt clasificate față de OWASP ASVS 4.0 și OWASP API Security Top 10 (2023). Gravitatea reflectă impact × probabilitate în contextul acestei aplicații.
Precizare legală
Aceasta este o evaluare tehnică comercială. Nu este un „audit de securitate cibernetică" în sensul OUG 155/2024, iar COXSWAIN nu este auditor atestat DNSC. Este o fotografie a stării codului la data evaluării; nu garantează absența altor probleme și nu constituie certificare de conformitate.
| ID | Gravitate | Constatare | Referință |
|---|---|---|---|
| COX-01 | Critic | Autorizare lipsă la nivel de obiect (IDOR) | API1:2023 |
| COX-02 | Critic | Secrete de producție în cod și în istoricul git | ASVS 2.10 |
| COX-03 | Ridicat | SQL injection prin concatenare de string | ASVS 5.3.4 |
| COX-04 | Ridicat | Fără limitare a ratei la autentificare | ASVS 2.2.1 |
| COX-05 | Ridicat | Dependențe cu vulnerabilități cunoscute | ASVS 14.2 |
| COX-06 | Mediu | Parole hash-uite cu algoritm rapid (SHA-256) | ASVS 2.4.1 |
| COX-07 | Mediu | JWT fără expirare, secret slab hardcodat | ASVS 3.5 |
| COX-08 | Mediu | Validare doar în frontend | ASVS 5.1.3 |
| COX-09 | Mediu | Erori înghițite, fără logare | ASVS 7.1 |
| COX-10 | Scăzut | Stack trace expus în producție | ASVS 7.4.1 |
router.get('/api/orders/:id', requireAuth, async (req, res) => {
// autentificat, dar NU verifică al cui e comanda
const order = await db.query(
'SELECT * FROM orders WHERE id = $1', [req.params.id]);
res.json(order.rows[0]);
});
Remediere
router.get('/api/orders/:id', requireAuth, async (req, res) => {
const order = await db.query(
'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
[req.params.id, req.user.id]);
if (!order.rows[0]) return res.status(404).json({ error: 'Not found' });
res.json(order.rows[0]);
});
De verificat și în alte părți: același tipar apare probabil la /api/bookings/:id și /api/invoices/:id. Recomand un middleware de verificare a proprietății, aplicat consistent.
Ordinea de remediere contează
const q = `SELECT * FROM products
WHERE name LIKE '%${req.query.term}%'`; // injectabil
const results = await db.query(q);
Remediere
const results = await db.query(
'SELECT * FROM products WHERE name LIKE $1',
[`%${req.query.term}%`]); // parametrizat
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, max: 5, // 5 încercări / 15 min / IP
message: 'Prea multe încercări. Reîncearcă în 15 minute.'
});
router.post('/api/login', loginLimiter, loginHandler);
Ideal, adaugă lockout progresiv pe cont și autentificare în doi factori (MFA) pentru conturile administrative.
| Pachet | Versiune | CVE | Problemă |
|---|---|---|---|
| jsonwebtoken | 8.3.0 | CVE-2022-23529 | Execuție de cod la verificarea token-ului |
| lodash | 4.17.11 | CVE-2019-10744 | Prototype pollution |
| axios | 0.21.0 | CVE-2020-28168 | SSRF |
Impact: variabil, dar jsonwebtoken vechi combinat cu JWT-ul slab din COX-07 e o combinație periculoasă. Remediere: npm audit fix, actualizare la versiunile cu patch, și integrarea trivy sau npm audit în CI ca poartă de blocare la fiecare build.
const hash = crypto.createHash('sha256')
.update(password).digest('hex'); // rapid = slab pentru parole
Remediere
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12); // lent, cu salt inclus
// verificare: await bcrypt.compare(password, hash);
Migrare: rehash la următorul login reușit al fiecărui utilizator, transparent.
jwt.sign({ userId: user.id }, 'secret123'); // secret slab, fără expirare
Remediere
jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET, // ≥256 biți, din secret manager
{ expiresIn: '15m' }); // + refresh tokens pentru sesiuni lungi
const { z } = require('zod');
const schema = z.object({
quantity: z.number().int().positive(),
date: z.string().datetime().refine(d => new Date(d) > new Date())
});
const parsed = schema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error);
Validarea din frontend e pentru comoditatea utilizatorului. Granița de securitate e serverul, mereu.
try {
await processPayment(order);
} catch (e) {} // eroarea dispare în tăcere
Remediere
try {
await processPayment(order);
} catch (e) {
logger.error({ err: e, orderId: order.id }, 'plata a eșuat');
await alertOnCriticalFailure(e); // monitorizare + alertă
throw e; // nu ascunde eșecul
}
const isProd = process.env.NODE_ENV === 'production';
res.status(500).json({
error: isProd ? 'Eroare internă' : err.message // stack doar în log
});
logger.error({ err }, 'eroare neprinsă');
Ordonat pe risc, nu pe efort. Începeți de sus.
| # | Acțiune | Efort |
|---|---|---|
| COX-02 | Rotește cele 3 secrete de producție (Stripe, DB, JWT). Sunt compromise. Restul curățării poate urma, dar rotația e acum. | 1-2 h |
| COX-01 | Adaugă verificarea proprietății pe /orders/:id și endpoint-urile similare. | 2-4 h |
| COX-03 | Parametrizează interogarea de căutare (și verifică restul codebase-ului pentru același tipar). | 1-2 h |
| # | Acțiune | Efort |
|---|---|---|
| COX-04 | Rate limiting la login + lockout progresiv | 2 h |
| COX-05 | Actualizare dependențe vulnerabile + npm audit în CI | 3-4 h |
| COX-07 | JWT: secret puternic din env + expirare + refresh tokens | 3-4 h |
| COX-06 | Migrare la bcrypt cu rehash la login | 3 h |
| # | Acțiune | Efort |
|---|---|---|
| COX-08 | Validare pe server pe toate endpoint-urile care primesc input | 4-6 h |
| COX-09 | Logare structurată + monitorizare + alerte pe eșecuri critice | 4 h |
| COX-10 | Ascunderea stack trace-ului în producție | 30 min |
Reverificare inclusă
După ce implementați remedierile, revin cu o reverificare a celor 10 constatări și confirm în scris ce e închis. Reverificarea e inclusă în această colaborare.
Uneltele automate au ridicat 47 de alerte. Am verificat manual fiecare. Iată bilanțul, și un exemplu de fals pozitiv, ca să vedeți ce înseamnă „triere".
| Sursă | Alerte brute | Reale | Zgomot |
|---|---|---|---|
| Semgrep CE | 31 | 6 | 25 |
| gitleaks | 4 | 1 | 3 |
| Trivy | 12 | 3 | 9 |
| Total | 47 | 10 | 34 |
const DEFAULT_PASSWORD_FIELD = 'password'; // numele câmpului, nu o parolă
De ce NU e o problemă: valoarea 'password' e numele unui câmp de formular, nu o parolă reală. Scannerul se declanșează pe cuvântul „password" în context de string. 34 din 47 de alerte au fost de acest tip. Un raport care le-ar fi inclus pe toate ar fi fost inutilizabil. Trierea e munca - și e diferența dintre un raport de inginer și un export de scanner.
COXSWAIN · S.C. COXSWAIN S.R.L. · CUI RO51383300 · București · coxswain.ro
Document model cu date fictive, folosit ca șablon. Un raport real e confidențial și specific unui singur client. Această evaluare reflectă starea codului la data indicată; nu garantează absența altor probleme și nu constituie audit reglementat sau certificare de conformitate.