Skip to content

Security Audit Report (Gotrace-portal — kl-audit v3.0) ​

  • Target Repository: Gotrace-portal (commit 93d9a64e03883e55050c9a41d66da41a863c368b)
  • Audit Scope: Toàn bộ 4 apps (apps/backend, apps/zalo-be, apps/integration-gateway, apps/frontend), 6 shared packages (packages/*), cấu hình hạ tầng (docker-compose*.yml, Dockerfile, deploy*.sh, ecosystem.config.js), Git history và dependency lockfile (yarn.lock).
  • Compliance Frameworks: PCI-DSS v4.0, OWASP ASVS v4.0.3 (Level 3), OWASP API Security Top 10 (2023), Thông tư 50/2024/TT-NHNN và Nghị định 13/2023/NĐ-CP (PDPD).
  • Methodology: 5-Stage Contextual AppSec Audit (Census toàn bộ 103 NestJS Controllers + Next.js Routes, Phân tích 8 Attack Surface Lenses, Đối chiếu Twin-Service Security Drift, Đánh giá Cross-Service Exploit Chains, và Whole-Repo Variant Sweep trên 100% — 575/575 Round 0 candidates).

1. Executive Summary ​

  • Go-Live Verdict: NO-GO (BẮT BUỘC REMEDIATE CÁC LỖ HỔNG CRITICAL/HIGH TRƯỚC KHI GO-LIVE PRODUCTION)
  • Kiến trúc Bảo mật Tích cực Hiện có (Positive Controls):
    • Instant JWT revocation qua token_version kết hợp Redis cache (JwtStrategy).
    • Ed25519 signature verification trên rawBody kèm kiểm tra timestamp skew (TIMESTAMP_SKEW_SEC) cho Spores Blockchain Webhook (SporesWebhookGuard).
    • Internal authentication dùng constant-time comparison crypto.timingSafeEqual trên loopback interface 127.0.0.1:3013 của apps/integration-gateway (x-internal-token + x-caller) và chặn open redirect với redirect: 'manual'.
    • Bảo vệ tính bất biến của Immutable Ledger sgtin_movements bằng PostgreSQL Trigger và chống race condition khi sinh mã tuần tự bằng Transactional Advisory Lock pg_advisory_xact_lock.

Tuy nhiên, quá trình Security Audit đã xác thực 16 lỗ hổng code & kiến trúc (SEC-01 đến SEC-16) và 3 hạng mục Dependency / Infrastructure (INFRA-01 đến INFRA-03), bao gồm 5 CRITICAL và 5 HIGH:

Finding IDVulnerability TitleSeverityCVSS v3.1CWE / Reference
SEC-01Broken Maker-Checker & Missing AdminGuard trên /api/v1/registrations: Any Authenticated User Can Approve Registrations, Hijack Existing Enterprise via Tax Code & Read Plaintext PasswordCRITICAL9.8CWE-284, CWE-862 / OWASP API5:2023
SEC-02Cross-Tenant BOLA/BFLA tại UsersController: Reset Password/Phone của User Bất kỳ, Privilege Escalation AGENT -> ADMIN, Arbitrary Role Assignment & Leak Password Hash qua get-staffs-by-companyCRITICAL9.6CWE-269, CWE-639 / OWASP API1:2023, API5:2023
SEC-03Cross-Tenant Leak NDATrace Token (companies.token) qua GET /api/v1/companies/:id & Mass Assignment trên Company ProfileCRITICAL9.1CWE-200, CWE-915 / OWASP API3:2023
SEC-04Cross-Tenant BOLA/IDOR trên 10 Modules (batches push dữ liệu giả lên National Trace Gateway, qr-codes, stamps đọc & recall tem, raw-material-zones, warehouses, company-certifications, input-sources, shipment-orders, vehicles, drivers) & BFLA trên 4 Admin/QLNN Modules (personnel, lookups, consultation-request, oversight-officers)CRITICAL9.1CWE-639, CWE-862 / OWASP API1:2023, API5:2023
SEC-05Committed 9 SQL Database Dumps (docs/db/*.sql), prisma/seed.ts Upsert Reset Default Password cho admin@gmail.com (role:1) trong deploy-prod.sh, 6.977 Bcrypt Hashes trong HanoiTrace Seed & Unenforced must_change_passwordCRITICAL9.1CWE-798, CWE-200 / NĐ 13/2023/NĐ-CP
SEC-06Stored XSS qua GTIN Description (normalizeRichHtml trên Public QR Trace & rehypeRaw trên Admin GTIN Table) Kết hợp Non-HttpOnly Token CookieHIGH8.6CWE-79, CWE-1004 / OWASP ASVS V5.3.3
SEC-07Twin-Service Security Drift tại apps/zalo-be: Wildcard CORS + Credentials, Missing Auth Rate-Limit, Bypass Subscription Service Gate, Pass-Through JwtStrategy & Unchecked ENTERPRISE Refresh Token, 9-Digit Suffix Phone MatchingHIGH8.3CWE-942, CWE-307, CWE-287 / OWASP API2:2023
SEC-08trust proxy: true & Raw X-Forwarded-For Parsing: Rate-Limit Bypass, 24h Victim IP Ban qua Honeypot includes(), SSR XFF Forwarding & Exposed NEXT_PUBLIC_ANTI_CRAWL_SECRETHIGH7.5CWE-290, CWE-400 / OWASP API4:2023
SEC-09RBAC Temporal Validity Bypass: JwtStrategy.validate() Bỏ qua Kiểm tra valid_from / valid_to của RoleAssignment Cho Tới Khi Cron Chạy (≤ 24h)MEDIUM6.8CWE-285, CWE-672 / OWASP API5:2023
SEC-10Short-Circuit Evaluation (||) Bypass Magic-Byte Validation trong MinioStorageService / S3StorageService & inline Content-Disposition tại PublicFilesControllerMEDIUM6.8CWE-434 / OWASP ASVS V12.1.1
SEC-11Session Management & CSRF Gaps: Refresh Token Không Revoke Khi Logout, changePassword Không Increment token_version, WebSocket/BullBoard Không Check tv & origin.startsWith Prefix BypassMEDIUM6.8CWE-352, CWE-613 / OWASP ASVS V3.3.2
SEC-12Unauthenticated NDATrace Webhook /api/v1/webhook: Replay id Ngoài Ciphertext, Ambiguous Company Lookup OR(taxCode|name|email) & Static ECDH Thiếu KDF/AADMEDIUM6.5CWE-306, CWE-294, CWE-327 / OWASP ASVS V2.9.1
SEC-13Unvalidated Dynamic orderBy Injection tại input-sources, batchs, users, registrations & Leak Raw Prisma exception.message khi HTTP 500MEDIUM6.5CWE-943, CWE-209 / OWASP API3:2023
SEC-14Unprotected Pre-Auth Cost-Sinks (Missing Turnstile/Captcha, Missing Global Circuit Breaker cho Email/VietQR/MinIO, Missing Multer fileSize Limit trên POST /registrations) & User EnumerationMEDIUM5.8CWE-204, CWE-400 / OWASP API4:2023
SEC-15Weak Default Credentials & Fail-Open ${VAR:-default} Fallbacks trong .env.staging.example, docker-compose.staging.yml, docker/docker-compose.yml (Bind 0.0.0.0) & Unchecked JWT_SECRET EntropyMEDIUM5.3CWE-1188 / OWASP ASVS V14.2.1
SEC-16Cross-Service Exploit Chain: Cross-Tenant Phone Update trên backend (PUT /users/:id) Kết hợp Auto-Bind zalo_user_id trên zalo-be Chiếm Quyền Vĩnh viễn Enterprise Nạn nhânHIGH8.5CWE-287, CWE-639 / OWASP API2:2023
INFRA-0192 Runtime-Reachable Dependency Vulnerabilities trong yarn.lock (next 16.1.6, axios 1.13.6, body-parser 2.2.2, path-to-regexp 8.3.0, proxy-addr 2.0.7, multer 2.1.1, nodemailer, xlsx 0.18.5, js-cookie 3.0.5)HIGH—OWASP A06:2021 — Vulnerable Components
INFRA-02Container Security Hardening (4 Dockerfiles thiếu USER node chạy dưới quyền root, bundle kèm devDependencies)LOW—CIS Docker Benchmark
INFRA-03Production Infrastructure Verification Checklist (Cloudflare/Nginx CF-Connecting-IP & INTERNAL_API_URL, Rotate companies.token với NDATrace, Verify DB Passwords & admin@gmail.com)VERIFY—Defense-in-Depth

2. Technical Deep Dive & Remediation Guide (SEC-*) ​

[SEC-01] Broken Maker-Checker trên /api/v1/registrations — Enterprise Account Takeover (CRITICAL — CVSS 9.8) ​

Location ​

  • apps/backend/src/modules/registrations/registrations.controller.ts:34-100
  • apps/backend/src/modules/registrations/registrations.service.ts:184-237, 542-690

Root Cause & Impact ​

  1. Các admin routes trong RegistrationsController (GET /stats, GET /:id, GET /, PUT /:id/approve, PUT /:id/reject) chỉ gắn @UseGuards(JwtAuthGuard) mà không có AdminGuard hay @CheckPermissions. Vì PermissionsGuard cho qua mọi route không gắn metadata, bất kỳ user nào đã đăng nhập (kể cả tài khoản PARENT_REP, STORE_STAFF, hoặc tài khoản đang bị SUSPENDED) đều gọi được API duyệt đơn PUT /api/v1/registrations/:id/approve!
  2. Trong RegistrationsService.create() (dòng 184–237), hệ thống chỉ check trùng tax_code trên bảng registrations mà không check bảng companies.
  3. Khi gọi RegistrationsService.approve() (dòng 588–603):
    typescript
    const existingCompany = await this.prisma.company.findFirst({
      where: { company_tax_code: registration.tax_code },
      select: { id: true },
    });
    if (existingCompany) {
      companyId = existingCompany.id;
      await this.prisma.company.update({
        where: { id: companyId },
        data: { organization_types: registration.organization_types },
      });
    }
    Hàm approve() lập tức tái sử dụng existingCompany.id của doanh nghiệp đang hoạt động có cùng MST, tạo một user mới gắn quyền chủ doanh nghiệp (ENT_OWNER / HIEU_TRUONG / SCHOOL_ADMIN) vào chính doanh nghiệp đó, và trả thẳng plaintext password (generated_password: randomPassword) trong HTTP response (dòng 690)!

Remediation ​

  1. Bổ sung AdminGuard vào toàn bộ các admin routes trong registrations.controller.ts:
    typescript
    @UseGuards(JwtAuthGuard, AdminGuard)
  2. Trong RegistrationsService.approve(), chặn việc tự động gắn owner mới vào doanh nghiệp đã có thành viên, tăng độ dài random password và không trả generated_password trong HTTP response:
    typescript
    if (existingCompany) {
      const memberCount = await this.prisma.user.count({
        where: { company_id: existingCompany.id, deleted_at: null },
      });
      if (memberCount > 0) {
        throw new ConflictException(
          'Doanh nghiệp đã có tài khoản chủ sở hữu — cần xác minh thủ công',
        );
      }
      companyId = existingCompany.id;
    }

[SEC-02] Cross-Tenant BOLA/BFLA tại UsersController & Privilege Escalation AGENT -> ADMIN (CRITICAL — CVSS 9.6) ​

Location ​

  • apps/backend/src/modules/users/users.controller.ts:109-115, 196-200
  • apps/backend/src/modules/users/users.service.ts:343-475, 518-553, 723-947, 1075-1140
  • apps/backend/src/common/guards/permission.guard.ts:35

Root Cause & Impact ​

  1. Cross-Tenant User Read, Update, Password Reset, Lock & Delete:
    • GET /api/v1/users/:id (findDetail), PUT /api/v1/users/:id (update), DELETE /api/v1/users/:id (remove), và PATCH /api/v1/users/:id/toggle-status (toggleStatus) không kiểm tra oldUser.company_id có thuộc tenant hoặc unit_path của currentUser hay không. Một admin của Enterprise A có thể reset password (dòng 807–809, không increment token_version) hoặc khóa tài khoản của Enterprise B hoặc cán bộ QLNN!
  2. Privilege Escalation từ AGENT lên ADMIN (role: 1):
    • Trong PermissionsGuard (permission.guard.ts:35), cả ROLE.ADMIN và ROLE.AGENT đều bypass kiểm tra @CheckPermissions.
    • Trong UsersService.update() (dòng 730–740) và create() (dòng 518–553), code chỉ chặn currentUser.role === ROLE.BUSINESS tạo role khác và chặn sửa ROLE.GOVERNMENT, nhưng không chặn ROLE.AGENT truyền role: 1 (ROLE.ADMIN) để tự nâng quyền chính mình hoặc tạo tài khoản ADMIN toàn hệ thống!
    • Ngoài ra, update() không gọi validateRoleAssignmentSubset() khi cập nhật role_assignment.role_id (dòng 823–867), và bản thân validateRoleAssignmentSubset() (dòng 1075–1105) chỉ kiểm tra allowed_account_types chứ chưa kiểm tra permissions subset.
  3. Password Hash Disclosure qua GET /api/v1/users/get-staffs-by-company/:companyId:
    • Route users.controller.ts:109-115 không gắn @CheckPermissions và gọi userRepository.findAll() không giới hạn select, trả về toàn bộ các cột của bảng users bao gồm cả bcrypt password hash của mọi nhân sự thuộc companyId bất kỳ!

Remediation ​

  • Xây dựng helper assertCanManageUser(actor: JwtPayload, targetId: bigint) trong UsersService để enforce target.company_id và target.unit_path trước mọi thao tác findDetail, update, remove, toggleStatus.
  • Chặn mọi role khác ROLE.ADMIN gán hoặc đổi role sang ROLE.ADMIN / ROLE.AGENT.
  • Bật global omit của Prisma trong PrismaService đối với user.password và company.token.

[SEC-03] Cross-Tenant Leak NDATrace Token (companies.token) & Mass Assignment trên Company Profile (CRITICAL — CVSS 9.1) ​

Location ​

  • apps/backend/src/modules/companies/companies.controller.ts:28-30, 62-65, 83-95
  • apps/backend/src/modules/companies/companies.service.ts:488-508, 617-647, 874-943

Root Cause & Impact ​

  1. CompaniesController chỉ gắn @UseGuards(JwtAuthGuard). Hai endpoint GET /api/v1/companies/:id (findDetail) và GET /api/v1/companies/:id/dashboard (getDashboard) không nhận @CurrentUser(), không check tenant isolation và query toàn bộ scalar columns của bảng companies — trả thẳng trường token (khóa xác thực tích hợp NDATrace của doanh nghiệp), did, và webhook_id cho bất kỳ user nào đã đăng nhập!
  2. Tại PUT /api/v1/companies/:id (companies.service.ts:874-943), biến isBusinessManager chỉ giới hạn ENT_OWNER và HIEU_TRUONG vào { organization_types }, còn mọi role khác (như ROLE.GOVERNMENT hoặc ROLE.AGENT, vì route không gắn @CheckPermissions nên rule chặn write đối với GOVERNMENT trong PermissionsGuard không chạy) đều rơi vào nhánh : companyData, cho phép ghi đè trực tiếp token, did, status, register_status, gcp, sale_user_id của bất kỳ doanh nghiệp nào!

Remediation ​

  • Truyền @CurrentUser() user: JwtPayload vào findOne và getDashboard, enforce tenant check theo user.company_id và omit trường token, webhook_id khỏi response (omit: { token: true, webhook_id: true }).
  • Trong CompaniesService.update(), chặn tuyệt đối user.role === ROLE.GOVERNMENT và strip các trường nhạy cảm (token, did, webhook_id, register_status, status, gcp, sale_user_id) khi user.role !== ROLE.ADMIN.

[SEC-04] Cross-Tenant BOLA/IDOR (10 Modules) & BFLA (4 Admin/QLNN Modules) (CRITICAL — CVSS 9.1) ​

Location & Impact ​

  1. Batches (BatchController & BatchService — Exploit Chain CH-2 Forged National Trace Sync):
    • apps/backend/src/modules/batchs/batch.controller.ts:61, 106-109, 147-148, 166-170, 182-183, 206-207, 230-231, 310-320 & batch.service.ts:605-686, 1943-1975
    • PUT /api/v1/batches/:id cập nhật batch của doanh nghiệp khác mà không check company_id, sau đó tự động gọi enqueueIntegrationEvent('batch.updated') (batch.service.ts:680-684) -> kích hoạt batchUpdatedPush (apps/integration-gateway/src/connectors/txng-national/index.ts:59) đẩy bản tin PUT LenhSanXuat giả mạo lên Cổng TXNG Quốc gia dưới danh nghĩa doanh nghiệp nạn nhân!
    • GET/POST /api/v1/batches/:id/export-sgtins tải toàn bộ mã SGTIN & QR URLs của doanh nghiệp khác; getHistory, findInventoryByBatch, findAll, getBatchByMaterial, getAllSgtin thiếu check company_id.
  2. Stamps (StampsController & StampsService):
    • apps/backend/src/modules/stamps/stamps.controller.ts:168-184, 221-285 & stamps.service.ts:438-499, 961-1116
    • Mặc dù model Stamp (schema.prisma:1032) có trực tiếp cột company_id BigInt, cả 2 hàm đọc (findOne, getHistory) và 5 hàm state mutation (suspendStamp, reactivateStamp, recallStamp, markDefective, reprintStamp) đều không check stamp.company_id. Đối thủ có thể gọi PUT /api/v1/stamps/:id/recall để recall tem của doanh nghiệp khác!
  3. QR Codes (QrCodesController & QrCodesService): apps/backend/src/modules/qr-codes/qr-codes.controller.ts:90-112 (GET /:batchId và DELETE /:batchId bỏ sót resolveBatchWithOwnerCheck).
  4. Raw Material Zones (RawMaterialZoneController): apps/backend/src/modules/raw-material-zones/raw-material-zone.controller.ts:167-177, 195-205 (GET /:id và DELETE /:id xóa vùng nguyên liệu của tenant khác).
  5. Company Certifications (CompanyCertificationsController): apps/backend/src/modules/company-certifications/company-certifications.controller.ts:171-181, 199-209 (GET /:id và DELETE /:id xóa chứng chỉ của tenant khác).
  6. Input Sources (InputSourcesService): apps/backend/src/modules/input-sources/input-sources.service.ts:391-397, 596-601 (link company_certification_ids của tenant khác vào input source của mình).
  7. Warehouses (WarehousesController): apps/backend/src/modules/warehouses/warehouses.controller.ts:155-221 (GET /:id và 6 route GET /:id/inventory/*, transactions, stock-take-items thiếu @CheckPermissions và thiếu check company_id, trong khi bản twin trên apps/zalo-be lại có check company_id).
  8. Shipment Orders, Vehicles, Drivers:
    • apps/backend/src/modules/shipment-orders/shipment-orders.controller.ts:146-155 (GET /:id)
    • apps/backend/src/modules/vehicles/vehicles.controller.ts:83-92 (GET /:id)
    • apps/backend/src/modules/drivers/drivers.controller.ts:83-92 (GET /:id)
  9. Inspector Personnel, System Lookups, Oversight Officers & Consultation Requests (BFLA):
    • apps/backend/src/modules/personnel/personnel.controller.ts:16-133 & apps/backend/src/modules/lookups/lookups.controller.ts:16-106: Chỉ dùng AdminOrActiveCompanyGuard (cho phép mọi user ROLE.BUSINESS đang active), dẫn tới nhân viên doanh nghiệp bất kỳ có thể thêm/sửa/xóa hồ sơ Cán bộ Thanh tra Nhà nước (/api/v1/personnel) và sửa/xóa extra_data của cây Đơn vị hành chính (/api/v1/lookups/items/:id) để thao túng danh sách cán bộ giám sát bếp ăn (getOversightOfficers)!
    • apps/backend/src/modules/users/users.controller.ts:117-124 (GET /api/v1/users/oversight-officers): Thiếu @CheckPermissions, lộ danh sách cán bộ giám sát cho mọi user.
    • apps/backend/src/modules/consultation-request/consultation-request.controller.ts:8-26: Chỉ dùng JwtAuthGuard mà không có AdminGuard cho /list và /edit.
    • Suspended Account Bypass: Vì hasValidAssignment chỉ được check bên trong PermissionsGuard (permission.guard.ts:71), mọi tài khoản đã bị SUSPENDED vẫn gọi được tất cả các route không gắn @CheckPermissions!

[SEC-05] Committed 9 SQL Dumps (docs/db/*.sql), prisma/seed.ts Reset Default ADMIN Password trong deploy-prod.sh & 6.977 Bcrypt Hashes trong Seed (CRITICAL — CVSS 9.1) ​

Location ​

  1. docs/db/stg_gotrace_1006.sql (26.5 MB), docs/db/gotrace_stg_0605.sql (14.6 MB), docs/db/gotrace_master_data.sql, docs/db/gotrace_0506.sql, docs/db/gotrace_0304.sql, docs/db/gotrace_3003.sql, docs/db/import_data.sql: Chứa companies.token, ~185 bcrypt password hashes, PII người tiêu dùng/cán bộ và chữ ký tay (shipment_pickups.*_signature).
  2. apps/backend/prisma/seed.ts:29-53, apps/zalo-be/prisma/seed.ts:16-26, deploy-prod.sh:709-718, 1226:
    • seed.ts chứa hardcoded default password, thực hiện prisma.user.upsert cho tài khoản admin@gmail.com (role: 1 — ROLE.ADMIN) với update: { password: hashedPassword } (overwrite password ADMIN về mặc định mỗi lần chạy seed!) và in password ra stdout. Nguy hiểm hơn, script deploy-prod.sh có tích hợp lệnh chạy seed và tài khoản admin@gmail.com có mặt trong cả 7 bản dump SQL!
  3. apps/backend/src/common/scripts/data/hanoitrace-*.data.ts (6.977 bcrypt hashes của 3.959 nhà cung cấp, 2.099 trường học, 756 phòng ban xã/phường, 126 xã/phường, 25 cơ quan NN, 12 UBND) kết hợp với việc jwt.strategy.ts:57-211 không enforce cờ must_change_password.
  4. Git history (commit 4503f0ea860812f9b3d3fc4c81e54cc1c616cd6a): .claude/settings.local.json:67-72 chứa live Bearer JWT.

Remediation ​

  • Trong apps/backend/prisma/seed.ts và apps/zalo-be/prisma/seed.ts: chặn chạy khi process.env.NODE_ENV === 'production', đổi update: { password: hashedPassword } thành update: {}, và gỡ lệnh seed khỏi deploy-prod.sh.
  • Purge các bản dump .sql và hashes trong file seed khỏi Git history bằng git filter-repo; rotate companies.token, JWT_SECRET và password của admin@gmail.com.
  • Enforce cờ must_change_password ở phía server trong Global Guard.

[SEC-06] Stored XSS qua GTIN Description trên Public QR Trace & Admin GTIN Table Kết hợp Non-HttpOnly Token Cookie (HIGH — CVSS 8.6) ​

  • Location: apps/frontend/utils/public-trace-format.ts:19-22 (normalizeRichHtml), product-overview-section.tsx:50, 98-106, components/common/markdown/markdown.tsx:5, 14 (rehypeRaw không có rehypeSanitize), components/admin/gtin/list/table.tsx:119, utils/token.ts:10-27.
  • Root Cause & Remediation: Trường description của GTIN nhận HTML/Markdown tùy ý. Khi render trên trang quét QR công khai (dangerouslySetInnerHTML gọi normalizeRichHtml(...)) và trên bảng danh sách GTIN của Admin (<Markdown> dùng rehypeRaw), mã HTML/JS không được sanitize. Vì accessToken và refreshToken (TTL 7d) được lưu trong document.cookie qua js-cookie, payload XSS chạy trên trình duyệt của Admin có thể đọc trực tiếp refreshToken để chiếm quyền điều khiển. Cần sanitize HTML bằng DOMPurify.sanitize() trong normalizeRichHtml(), thêm rehypeSanitize sau rehypeRaw trong markdown.tsx, và chuyển refreshToken sang cookie HttpOnly; Secure; SameSite=Strict.

[SEC-07] Twin-Service Security Drift tại apps/zalo-be (HIGH — CVSS 8.3) ​

  1. apps/zalo-be/src/main.ts:36: app.enableCors({ origin: true, credentials: true }) reflect mọi Origin kèm credentials.
  2. apps/zalo-be/src/app.module.ts:53-54: Không đăng ký ThrottlerGuard làm APP_GUARD.
  3. apps/zalo-be/src/modules/auth/auth.service.ts:353-427, 549-593: Hàm buildRoles() và switchToEnterprise() không check trạng thái dịch vụ của doanh nghiệp (companies.status / enforceServiceGate), cho phép doanh nghiệp hết hạn subscription bypass qua Zalo.
  4. apps/zalo-be/src/modules/auth/auth.service.ts:282-340 & jwt.strategy.ts:23-28: Hàm refresh() chỉ re-validate DB cho DRIVER và CONSUMER, bỏ qua hoàn toàn role ENTERPRISE, đồng thời JwtStrategy chỉ pass-through payload mà không check DB hay token_version.
  5. apps/zalo-be/src/modules/auth/auth.service.ts:51-53, 360, 368, 436, 473-480 & zalo-webhook.service.ts:17-40: Match SĐT bằng 9 số cuối (endsWith) và thiếu kiểm tra replay window trên Webhook Zalo.

[SEC-08] trust proxy: true & Raw X-Forwarded-For Parsing: Rate-Limit Bypass & 24h Victim IP Ban (HIGH — CVSS 7.5) ​

  • Location: apps/backend/src/main.ts:45, 177, apps/backend/src/common/middlewares/anti-crawl.middleware.ts:37-54, 82, 122-129, apps/backend/src/common/interceptors/anti-scan.interceptor.ts:87-94, apps/frontend/lib/anti-crawl.ts:6-7
  • Remediation: Cấu hình app.set('trust proxy', process.env.TRUSTED_PROXIES?.split(',') ?? 'loopback'), dùng req.ip đã xác thực qua reverse proxy, đổi requestPath.includes(HONEYPOT_ROUTE) thành exact match req.path === HONEYPOT_ROUTE, và gỡ bỏ secret khỏi biến client-side NEXT_PUBLIC_ANTI_CRAWL_SECRET.

[SEC-09] đến [SEC-16] & Infrastructure Findings (INFRA-01 .. INFRA-03) ​

  • [SEC-09] (MEDIUM — RBAC Temporal Validity Bypass): Tại apps/backend/src/modules/auth/jwt.strategy.ts:18-23, 58, 98-192, biến now và NO_VALID_SCOPE_SENTINEL ('0') bị bỏ không, khiến RoleAssignment đã hết hạn vẫn dùng được cho tới khi cron nửa đêm chạy. Remediation: Filter (!ra.valid_from || ra.valid_from <= now) && (!ra.valid_to || ra.valid_to >= now) ngay trong JwtStrategy.validate().
  • [SEC-10] (MEDIUM — Short-Circuit || Bypass Magic-Byte Validation): Tại minio-storage.service.ts:123-124 và s3-storage.service.ts:128-129, sửa options?.contentType || this.fileValidation.validateMimeType(buffer) thành luôn gọi this.fileValidation.validateMimeType(buffer, options?.contentType), xóa nhánh pass-through khi !detectedType trong file-validation.helper.ts:45-48, và chỉ trả inline cho image/(png|jpeg|webp) tại public-files.controller.ts:97-100.
  • [SEC-11] (MEDIUM — Session Management & CSRF): Lưu jti của Refresh Token trên Redis để revoke khi logout; increment token_version: { increment: 1 } trong AuthService.changePassword() (auth.service.ts:356-362); check tv trên WebSocket /ws/monitoring và Bull Board; sửa origin.startsWith(o) tại main.ts:133 thành exact origin match new URL(origin).origin.
  • [SEC-12] (MEDIUM — NDATrace Webhook /api/v1/webhook): Tại webhook.controller.ts:5-12 và webhook.service.ts:30-41, 85-123, bổ sung signature/mTLS guard, đưa id chống replay vào bên trong encrypted payload, chỉ match doanh nghiệp theo unique company_tax_code thay vì OR(taxCode | name | email), và bổ sung HKDF + AAD cho ECDH trong cryto.ts:84-179.
  • [SEC-13] (MEDIUM — Dynamic orderBy Injection & Raw Prisma Error Leak): Validate cột sort theo allowlist (ALLOWED_SORT_COLUMNS) tại input-sources.service.ts:102, batch.service.ts:1264, users.service.ts:239, registrations.service.ts:378; đồng thời tại http-exception.filter.ts:63-65 không trả thẳng exception.message của Prisma về cho client khi xảy ra lỗi 500.
  • [SEC-14] (MEDIUM — Pre-Auth Cost-Sinks & User Enumeration): Bổ sung Cloudflare Turnstile và Global Circuit Breaker cho các route gửi email / gọi VietQR / upload ảnh public; thêm { limits: { fileSize: 5 * 1024 * 1024 } } cho registrations.controller.ts:36; luôn chạy bcrypt.compare với DUMMY_HASH khi không tìm thấy email trong AuthService.login().
  • [SEC-15] (MEDIUM — Default Credentials & Fail-Open Staging Fallbacks): Đổi cú pháp ${VAR:-default} trong docker-compose.staging.yml thành ${VAR:?required}, bind 127.0.0.1 cho docker/docker-compose.yml:11, 30, 51, và enforce độ dài tối thiểu 256-bit cho JWT_SECRET lúc startup (auth.service.ts:176-178).
  • [SEC-16] (HIGH — Cross-Service Exploit Chain backend -> zalo-be): Kết hợp lỗi PUT /api/v1/users/:id thiếu tenant check (users.service.ts:775-813, cho phép đổi phone_number của user thuộc tenant khác và clear zalo_user_id) với cơ chế tự động bind zalo_user_id theo SĐT trên apps/zalo-be (auth.service.ts:353-446), kẻ tấn công có thể chiếm quyền truy cập doanh nghiệp nạn nhân qua Zalo MiniApp với refresh token 30 ngày ngay cả khi nạn nhân đổi mật khẩu web.
  • [INFRA-01] (HIGH — 92 Runtime-Reachable Dependency Vulnerabilities trong yarn.lock): Upgrade next >= 16.4.1, và thêm resolutions trong root package.json cho axios >= 1.20.0, body-parser >= 2.4.0, path-to-regexp >= 8.4.0, proxy-addr >= 2.0.8, multer >= 2.5.0, js-cookie >= 3.0.7, thay thế xlsx (0.18.5) bằng exceljs và xóa file package-lock.json dư thừa.
  • [INFRA-02] (LOW — Container Hardening): Thêm USER node, loại bỏ devDependencies ở runner stage của cả 4 Dockerfile (node:22-alpine), và bổ sung security_opt: ["no-new-privileges:true"], cap_drop: ["ALL"].
  • [INFRA-03] (Production Infrastructure Verification): Đội ngũ DevOps & Integration cần xác minh: (1) Cấu hình Nginx/Cloudflare CF-Connecting-IP và biến INTERNAL_API_URL, (2) Phối hợp NDATrace rotate toàn bộ companies.token, và (3) Xác nhận password PostgreSQL/Redis/MinIO và password tài khoản admin@gmail.com trên Production khác hoàn toàn các giá trị mặc định.

Technical Documentation & Security Audit Report — kl-audit v3.0