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/575Round 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_versionkết hợp Redis cache (JwtStrategy). - Ed25519 signature verification trên
rawBodykèm kiểm tra timestamp skew (TIMESTAMP_SKEW_SEC) cho Spores Blockchain Webhook (SporesWebhookGuard). - Internal authentication dùng constant-time comparison
crypto.timingSafeEqualtrên loopback interface127.0.0.1:3013củaapps/integration-gateway(x-internal-token+x-caller) và chặn open redirect vớiredirect: 'manual'. - Bảo vệ tính bất biến của Immutable Ledger
sgtin_movementsbằng PostgreSQL Trigger và chống race condition khi sinh mã tuần tự bằng Transactional Advisory Lockpg_advisory_xact_lock.
- Instant JWT revocation qua
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 ID | Vulnerability Title | Severity | CVSS v3.1 | CWE / Reference |
|---|---|---|---|---|
| SEC-01 | Broken Maker-Checker & Missing AdminGuard trên /api/v1/registrations: Any Authenticated User Can Approve Registrations, Hijack Existing Enterprise via Tax Code & Read Plaintext Password |
CRITICAL | 9.8 | CWE-284, CWE-862 / OWASP API5:2023 |
| SEC-02 | Cross-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-company |
CRITICAL | 9.6 | CWE-269, CWE-639 / OWASP API1:2023, API5:2023 |
| SEC-03 | Cross-Tenant Leak NDATrace Token (companies.token) qua GET /api/v1/companies/:id & Mass Assignment trên Company Profile |
CRITICAL | 9.1 | CWE-200, CWE-915 / OWASP API3:2023 |
| SEC-04 | Cross-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) |
CRITICAL | 9.1 | CWE-639, CWE-862 / OWASP API1:2023, API5:2023 |
| SEC-05 | Committed 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_password |
CRITICAL | 9.1 | CWE-798, CWE-200 / NĐ 13/2023/NĐ-CP |
| SEC-06 | Stored XSS qua GTIN Description (normalizeRichHtml trên Public QR Trace & rehypeRaw trên Admin GTIN Table) Kết hợp Non-HttpOnly Token Cookie |
HIGH | 8.6 | CWE-79, CWE-1004 / OWASP ASVS V5.3.3 |
| SEC-07 | Twin-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 Matching |
HIGH | 8.3 | CWE-942, CWE-307, CWE-287 / OWASP API2:2023 |
| SEC-08 | trust 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_SECRET |
HIGH | 7.5 | CWE-290, CWE-400 / OWASP API4:2023 |
| SEC-09 | RBAC 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) |
MEDIUM | 6.8 | CWE-285, CWE-672 / OWASP API5:2023 |
| SEC-10 | Short-Circuit Evaluation (||) Bypass Magic-Byte Validation trong MinioStorageService / S3StorageService & inline Content-Disposition tại PublicFilesController |
MEDIUM | 6.8 | CWE-434 / OWASP ASVS V12.1.1 |
| SEC-11 | Session 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 Bypass |
MEDIUM | 6.8 | CWE-352, CWE-613 / OWASP ASVS V3.3.2 |
| SEC-12 | Unauthenticated NDATrace Webhook /api/v1/webhook: Replay id Ngoài Ciphertext, Ambiguous Company Lookup OR(taxCode|name|email) & Static ECDH Thiếu KDF/AAD |
MEDIUM | 6.5 | CWE-306, CWE-294, CWE-327 / OWASP ASVS V2.9.1 |
| SEC-13 | Unvalidated Dynamic orderBy Injection tại input-sources, batchs, users, registrations & Leak Raw Prisma exception.message khi HTTP 500 |
MEDIUM | 6.5 | CWE-943, CWE-209 / OWASP API3:2023 |
| SEC-14 | Unprotected Pre-Auth Cost-Sinks (Missing Turnstile/Captcha, Missing Global Circuit Breaker cho Email/VietQR/MinIO, Missing Multer fileSize Limit trên POST /registrations) & User Enumeration |
MEDIUM | 5.8 | CWE-204, CWE-400 / OWASP API4:2023 |
| SEC-15 | Weak 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 Entropy |
MEDIUM | 5.3 | CWE-1188 / OWASP ASVS V14.2.1 |
| SEC-16 | Cross-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ân |
HIGH | 8.5 | CWE-287, CWE-639 / OWASP API2:2023 |
| INFRA-01 | 92 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-02 | Container Security Hardening (4 Dockerfiles thiếu USER node chạy dưới quyền root, bundle kèm devDependencies) |
LOW | — | CIS Docker Benchmark |
| INFRA-03 | Production 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-100apps/backend/src/modules/registrations/registrations.service.ts:184-237, 542-690
Root Cause & Impact ​
- 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óAdminGuardhay@CheckPermissions. VìPermissionsGuardcho qua mọi route không gắn metadata, bất kỳ user nào đã đăng nhập (kể cả tài khoảnPARENT_REP,STORE_STAFF, hoặc tài khoản đang bịSUSPENDED) đều gọi được API duyệt đơnPUT /api/v1/registrations/:id/approve! - Trong
RegistrationsService.create()(dòng 184–237), hệ thống chỉ check trùngtax_codetrên bảngregistrationsmà không check bảngcompanies. - Khi gọi
RegistrationsService.approve()(dòng 588–603):typescriptHàmconst 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 }, }); }approve()lập tức tái sử dụngexistingCompany.idcủ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 ​
- Bổ sung
AdminGuardvào toàn bộ các admin routes trongregistrations.controller.ts:typescript@UseGuards(JwtAuthGuard, AdminGuard) - 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_passwordtrong HTTP response:typescriptif (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-200apps/backend/src/modules/users/users.service.ts:343-475, 518-553, 723-947, 1075-1140apps/backend/src/common/guards/permission.guard.ts:35
Root Cause & Impact ​
- 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 traoldUser.company_idcó thuộc tenant hoặcunit_pathcủacurrentUserhay không. Một admin của Enterprise A có thể reset password (dòng 807–809, không incrementtoken_version) hoặc khóa tài khoản của Enterprise B hoặc cán bộ QLNN!
- Privilege Escalation từ
AGENTlênADMIN(role: 1):- Trong
PermissionsGuard(permission.guard.ts:35), cảROLE.ADMINvàROLE.AGENTđều bypass kiểm tra@CheckPermissions. - Trong
UsersService.update()(dòng 730–740) vàcreate()(dòng 518–553), code chỉ chặncurrentUser.role === ROLE.BUSINESStạo role khác và chặn sửaROLE.GOVERNMENT, nhưng không chặnROLE.AGENTtruyềnrole: 1(ROLE.ADMIN) để tự nâng quyền chính mình hoặc tạo tài khoảnADMINtoàn hệ thống! - Ngoài ra,
update()không gọivalidateRoleAssignmentSubset()khi cập nhậtrole_assignment.role_id(dòng 823–867), và bản thânvalidateRoleAssignmentSubset()(dòng 1075–1105) chỉ kiểm traallowed_account_typeschứ chưa kiểm trapermissionssubset.
- Trong
- Password Hash Disclosure qua
GET /api/v1/users/get-staffs-by-company/:companyId:- Route
users.controller.ts:109-115không gắn@CheckPermissionsvà gọiuserRepository.findAll()không giới hạnselect, trả về toàn bộ các cột của bảngusersbao gồm cả bcryptpasswordhash của mọi nhân sự thuộccompanyIdbất kỳ!
- Route
Remediation ​
- Xây dựng helper
assertCanManageUser(actor: JwtPayload, targetId: bigint)trongUsersServiceđể enforcetarget.company_idvàtarget.unit_pathtrước mọi thao tácfindDetail,update,remove,toggleStatus. - Chặn mọi role khác
ROLE.ADMINgán hoặc đổirolesangROLE.ADMIN/ROLE.AGENT. - Bật global
omitcủa Prisma trongPrismaServiceđối vớiuser.passwordvà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-95apps/backend/src/modules/companies/companies.service.ts:488-508, 617-647, 874-943
Root Cause & Impact ​
CompaniesControllerchỉ gắn@UseGuards(JwtAuthGuard). Hai endpointGET /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ảngcompanies— trả thẳng trườngtoken(khóa xác thực tích hợp NDATrace của doanh nghiệp),did, vàwebhook_idcho bất kỳ user nào đã đăng nhập!- Tại
PUT /api/v1/companies/:id(companies.service.ts:874-943), biếnisBusinessManagerchỉ giới hạnENT_OWNERvàHIEU_TRUONGvào{ organization_types }, còn mọi role khác (nhưROLE.GOVERNMENThoặcROLE.AGENT, vì route không gắn@CheckPermissionsnên rule chặn write đối vớiGOVERNMENTtrongPermissionsGuardkhông chạy) đều rơi vào nhánh: companyData, cho phép ghi đè trực tiếptoken,did,status,register_status,gcp,sale_user_idcủa bất kỳ doanh nghiệp nào!
Remediation ​
- Truyền
@CurrentUser() user: JwtPayloadvàofindOnevàgetDashboard, enforce tenant check theouser.company_idvà omit trườngtoken,webhook_idkhỏi response (omit: { token: true, webhook_id: true }). - Trong
CompaniesService.update(), chặn tuyệt đốiuser.role === ROLE.GOVERNMENTvà strip các trường nhạy cảm (token,did,webhook_id,register_status,status,gcp,sale_user_id) khiuser.role !== ROLE.ADMIN.
[SEC-04] Cross-Tenant BOLA/IDOR (10 Modules) & BFLA (4 Admin/QLNN Modules) (CRITICAL — CVSS 9.1) ​
Location & Impact ​
- Batches (
BatchController&BatchService— Exploit ChainCH-2Forged 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-1975PUT /api/v1/batches/:idcập nhật batch của doanh nghiệp khác mà không checkcompany_id, sau đó tự động gọienqueueIntegrationEvent('batch.updated')(batch.service.ts:680-684) -> kích hoạtbatchUpdatedPush(apps/integration-gateway/src/connectors/txng-national/index.ts:59) đẩy bản tinPUT LenhSanXuatgiả 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-sgtinstải toàn bộ mã SGTIN & QR URLs của doanh nghiệp khác;getHistory,findInventoryByBatch,findAll,getBatchByMaterial,getAllSgtinthiếu checkcompany_id.
- 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ộtcompany_id BigInt, cả 2 hàm đọc (findOne,getHistory) và 5 hàm state mutation (suspendStamp,reactivateStamp,recallStamp,markDefective,reprintStamp) đều không checkstamp.company_id. Đối thủ có thể gọiPUT /api/v1/stamps/:id/recallđể recall tem của doanh nghiệp khác!
- QR Codes (
QrCodesController&QrCodesService):apps/backend/src/modules/qr-codes/qr-codes.controller.ts:90-112(GET /:batchIdvàDELETE /:batchIdbỏ sótresolveBatchWithOwnerCheck). - Raw Material Zones (
RawMaterialZoneController):apps/backend/src/modules/raw-material-zones/raw-material-zone.controller.ts:167-177, 195-205(GET /:idvàDELETE /:idxóa vùng nguyên liệu của tenant khác). - Company Certifications (
CompanyCertificationsController):apps/backend/src/modules/company-certifications/company-certifications.controller.ts:171-181, 199-209(GET /:idvàDELETE /:idxóa chứng chỉ của tenant khác). - Input Sources (
InputSourcesService):apps/backend/src/modules/input-sources/input-sources.service.ts:391-397, 596-601(linkcompany_certification_idscủa tenant khác vào input source của mình). - Warehouses (
WarehousesController):apps/backend/src/modules/warehouses/warehouses.controller.ts:155-221(GET /:idvà 6 routeGET /:id/inventory/*,transactions,stock-take-itemsthiếu@CheckPermissionsvà thiếu checkcompany_id, trong khi bản twin trênapps/zalo-belại có checkcompany_id). - 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)
- 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ùngAdminOrActiveCompanyGuard(cho phép mọi userROLE.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óaextra_datacủ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ùngJwtAuthGuardmà không cóAdminGuardcho/listvà/edit.- Suspended Account Bypass: Vì
hasValidAssignmentchỉ được check bên trongPermissionsGuard(permission.guard.ts:71), mọi tài khoản đã bịSUSPENDEDvẫ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 ​
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ứacompanies.token, ~185 bcrypt password hashes, PII người tiêu dùng/cán bộ và chữ ký tay (shipment_pickups.*_signature).apps/backend/prisma/seed.ts:29-53,apps/zalo-be/prisma/seed.ts:16-26,deploy-prod.sh:709-718, 1226:seed.tschứa hardcoded default password, thực hiệnprisma.user.upsertcho tài khoảnadmin@gmail.com(role: 1—ROLE.ADMIN) vớiupdate: { 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, scriptdeploy-prod.shcó tích hợp lệnh chạyseedvà tài khoảnadmin@gmail.comcó mặt trong cả 7 bản dump SQL!
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ệcjwt.strategy.ts:57-211không enforce cờmust_change_password.- Git history (
commit 4503f0ea860812f9b3d3fc4c81e54cc1c616cd6a):.claude/settings.local.json:67-72chứa live Bearer JWT.
Remediation ​
- Trong
apps/backend/prisma/seed.tsvàapps/zalo-be/prisma/seed.ts: chặn chạy khiprocess.env.NODE_ENV === 'production', đổiupdate: { password: hashedPassword }thànhupdate: {}, và gỡ lệnhseedkhỏideploy-prod.sh. - Purge các bản dump
.sqlvà hashes trong file seed khỏi Git history bằnggit filter-repo; rotatecompanies.token,JWT_SECRETvà password củaadmin@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(rehypeRawkhông córehypeSanitize),components/admin/gtin/list/table.tsx:119,utils/token.ts:10-27. - Root Cause & Remediation: Trường
descriptioncủa GTIN nhận HTML/Markdown tùy ý. Khi render trên trang quét QR công khai (dangerouslySetInnerHTMLgọinormalizeRichHtml(...)) và trên bảng danh sách GTIN của Admin (<Markdown>dùngrehypeRaw), mã HTML/JS không được sanitize. VìaccessTokenvàrefreshToken(TTL 7d) được lưu trongdocument.cookiequajs-cookie, payload XSS chạy trên trình duyệt của Admin có thể đọc trực tiếprefreshTokenđể chiếm quyền điều khiển. Cần sanitize HTML bằngDOMPurify.sanitize()trongnormalizeRichHtml(), thêmrehypeSanitizesaurehypeRawtrongmarkdown.tsx, và chuyểnrefreshTokensang cookieHttpOnly; Secure; SameSite=Strict.
[SEC-07] Twin-Service Security Drift tại apps/zalo-be (HIGH — CVSS 8.3) ​
apps/zalo-be/src/main.ts:36:app.enableCors({ origin: true, credentials: true })reflect mọi Origin kèm credentials.apps/zalo-be/src/app.module.ts:53-54: Không đăng kýThrottlerGuardlàmAPP_GUARD.apps/zalo-be/src/modules/auth/auth.service.ts:353-427, 549-593: HàmbuildRoles()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.apps/zalo-be/src/modules/auth/auth.service.ts:282-340&jwt.strategy.ts:23-28: Hàmrefresh()chỉ re-validate DB choDRIVERvàCONSUMER, bỏ qua hoàn toàn roleENTERPRISE, đồng thờiJwtStrategychỉ pass-through payload mà không check DB haytoken_version.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ùngreq.ipđã xác thực qua reverse proxy, đổirequestPath.includes(HONEYPOT_ROUTE)thành exact matchreq.path === HONEYPOT_ROUTE, và gỡ bỏ secret khỏi biến client-sideNEXT_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ếnnowvàNO_VALID_SCOPE_SENTINEL('0') bị bỏ không, khiếnRoleAssignmentđã 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 trongJwtStrategy.validate(). - [SEC-10] (MEDIUM — Short-Circuit
||Bypass Magic-Byte Validation): Tạiminio-storage.service.ts:123-124vàs3-storage.service.ts:128-129, sửaoptions?.contentType || this.fileValidation.validateMimeType(buffer)thành luôn gọithis.fileValidation.validateMimeType(buffer, options?.contentType), xóa nhánh pass-through khi!detectedTypetrongfile-validation.helper.ts:45-48, và chỉ trảinlinechoimage/(png|jpeg|webp)tạipublic-files.controller.ts:97-100. - [SEC-11] (MEDIUM — Session Management & CSRF): Lưu
jticủa Refresh Token trên Redis để revoke khilogout; incrementtoken_version: { increment: 1 }trongAuthService.changePassword()(auth.service.ts:356-362); checktvtrên WebSocket/ws/monitoringvà Bull Board; sửaorigin.startsWith(o)tạimain.ts:133thành exact origin matchnew URL(origin).origin. - [SEC-12] (MEDIUM — NDATrace Webhook
/api/v1/webhook): Tạiwebhook.controller.ts:5-12vàwebhook.service.ts:30-41, 85-123, bổ sung signature/mTLS guard, đưaidchống replay vào bên trong encrypted payload, chỉ match doanh nghiệp theo uniquecompany_tax_codethay vìOR(taxCode | name | email), và bổ sung HKDF + AAD cho ECDH trongcryto.ts:84-179. - [SEC-13] (MEDIUM — Dynamic
orderByInjection & Raw Prisma Error Leak): Validate cột sort theo allowlist (ALLOWED_SORT_COLUMNS) tạiinput-sources.service.ts:102,batch.service.ts:1264,users.service.ts:239,registrations.service.ts:378; đồng thời tạihttp-exception.filter.ts:63-65không trả thẳngexception.messagecủ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 } }choregistrations.controller.ts:36; luôn chạybcrypt.comparevớiDUMMY_HASHkhi không tìm thấy email trongAuthService.login(). - [SEC-15] (MEDIUM — Default Credentials & Fail-Open Staging Fallbacks): Đổi cú pháp
${VAR:-default}trongdocker-compose.staging.ymlthành${VAR:?required}, bind127.0.0.1chodocker/docker-compose.yml:11, 30, 51, và enforce độ dài tối thiểu 256-bit choJWT_SECRETlúc startup (auth.service.ts:176-178). - [SEC-16] (HIGH — Cross-Service Exploit Chain
backend->zalo-be): Kết hợp lỗiPUT /api/v1/users/:idthiếu tenant check (users.service.ts:775-813, cho phép đổiphone_numbercủa user thuộc tenant khác và clearzalo_user_id) với cơ chế tự động bindzalo_user_idtheo SĐT trênapps/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): Upgradenext >= 16.4.1, và thêmresolutionstrong rootpackage.jsonchoaxios >= 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ằngexceljsvà xóa filepackage-lock.jsondư thừa. - [INFRA-02] (LOW — Container Hardening): Thêm
USER node, loại bỏdevDependenciesở runner stage của cả 4Dockerfile(node:22-alpine), và bổ sungsecurity_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-IPvà biếnINTERNAL_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ảnadmin@gmail.comtrên Production khác hoàn toàn các giá trị mặc định.