5.8 · บัญชีหนี้ทางเทคนิคด้านความปลอดภัย (Security debt)
บทนี้รวบรวมช่องโหว่และหนี้ทางเทคนิคด้าน security ของ centralize-api แบ่งตามระดับความรุนแรง (Critical / High / Medium / Low) พร้อมวิธีการแก้ไขและผลกระทบต่อระบบ VTRC
เกณฑ์กำหนดระดับความรุนแรง
| ระดับ | นิยาม |
|---|---|
| Critical | ถูกโจมตีได้ทันที → data breach, RCE, หรือ auth bypass เมื่อ attacker เข้าถึง endpoint |
| High | ต้องมีเงื่อนไขเล็กน้อย (เช่น internal network access) แต่ exploit แล้วเสียหายสูง |
| Medium | ต้องมีเงื่อนไขมากขึ้น หรือเป็นช่องโหว่ในเลเยอร์ป้องกันเชิงลึก (defense-in-depth) |
| Low | Code smell ที่ไม่ถูกโจมตีได้โดยตรง แต่ควรแก้เพื่อความสะดวกในการดูแลรักษา |
Critical (4 รายการ)
CRIT-01 · .env committed มี plaintext DB password
| ไฟล์ | centralize-api/.env:1-15 |
| ปัญหา | .env ถูก force-add เข้า git แม้ .gitignore บอกว่า ignore .env* → ไฟล์อยู่ใน git history ตั้งแต่ commit แรก |
| ข้อมูลที่รั่ว | DB_PASSWORD=Xfwo_gawPp.!, DB_USER=CENTRALIZE_APP_UAT, DB_HOST=10.188.128.58, DB_PORT=1433 |
| ผลกระทบ | ใครก็ตามที่เข้าถึง git history สามารถ connect ไป HRMI UAT database ได้โดยตรง ดู/แก้/ลบข้อมูล HR ของพนักงาน |
| การแก้ไข | (1) เปลี่ยน DB password ทันที (2) ลบ .env ออกจาก git history ด้วย git filter-repo (3) เพิ่ม secret manager (Vault, AWS Secrets Manager) (4) ใช้ .env.example เป็น template แทน |
| แรงที่ต้องใช้ | 1-2 วัน (รวมเปลี่ยน password และประสานงานกับ DBA) |
CRIT-02 · JWT secret เป็น SSH pubkey fragment
| ไฟล์ | centralize-api/.env:15 |
| ปัญหา | SECRET_KEY=AAAAB3NzaC1yc2E... เป็น base64 ของ ssh-rsa (SSH public key) ไม่ใช่ random secret → key นี้เป็น public key ที่คนที่ generate รู้อยู่แล้ว และ commit ใน repo |
| ผลกระทบ | ถ้า guard ถูกเปิดใช้ (option A ในบท 5.5) ใครก็ปลอมแปลง JWT ได้เพราะรู้ secret |
| ปัจจุบัน | guard ถูก bypass (CRIT-03) → ไม่ถูก exploit ทันที แต่เป็นอันตรายที่ซ่อนรอ |
| การแก้ไข | สุ่ม secret ใหม่ด้วย openssl rand -hex 64 และเก็บใน secret manager |
| แรงที่ต้องใช้ | 0.5 วัน |
CRIT-03 · Auth bypass — @Public() ทุก route
| ไฟล์ | ทุก controller method — ดูบท 5.5 |
| ปัญหา | JwtAuthGuard ลงทะเบียนเป็น global guard แต่ทุก method ใช้ @Public() → auth ไม่ทำงานเลย |
| ผลกระทบ | ทุก endpoint เปิดให้ใครก็เรียกได้ → PII (empCode, fullName, identityCard, bankAccount, payslip detail) รั่วทั้งหมด |
| ปัจจัยบรรเทา | ถ้า service อยู่ใน private network (ไม่ expose internet) ความเสี่ยงลดลง แต่ก็ยังไม่ดี |
| การแก้ไข | อย่างน้อยเพิ่ม apiKey middleware (Option B ในบท 5.5) ก่อน แล้วค่อยตามด้วย JWT จริงจัง |
| แรงที่ต้องใช้ | 1 วัน (apiKey) — 3 วัน (JWT เต็มรูปแบบ + login endpoint) |
CRIT-04 · PII exposure โดยไม่มี auth
| ไฟล์ | ทุก endpoint ที่ return PII — /api/employee, /api/employee/bankAccount, /api/hrPaySlip, /api/hrTimeTemp/import |
| ปัญหา | Endpoint เหล่านี้ return ข้อมูลพนักงาน (รหัสบัตรประชาชน, เลขบัญชี, สลิปเงินเดือน) โดยไม่มี auth → ใครก็ดึงได้ |
| PDPA compliance | ละเมิดพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562 (PDPA) มาตรา 26 (การเก็บรวบรวมโดยไม่ได้รับความยินยอม) |
| ผลกระทบ | บริษัท และ สภากาชาด อาจถูกฟ้อง + ถูกปรับสูงสุด 5 ล้านบาท |
| การแก้ไข | แก้ตาม CRIT-03 ก่อน แล้วเพิ่ม audit log ที่บันทึกใครเข้าถึง PII อะไรบ้าง |
| แรงที่ต้องใช้ | รวมกับ CRIT-03 |
High (7 รายการ)
HIGH-01 · logging: ['query'] รั่ว PII ลง log
| ไฟล์ | centralize-api/src/app.module.ts:31, 50 (TypeORM config ของทั้ง 2 connections) |
| ปัญหา | logging: ['error', 'query'] → ทุก SQL query และผลลัพธ์ถูก log ผ่าน I3GatewayLogger ลงไฟล์ ${cwd}/logs/DD-MM-YYYY.log |
| ข้อมูลที่รั่ว | empCode, fullName, identityCard (รหัสบัตร), bankAccount (เลขบัญชี), payslip detail |
| ผลกระทบ | log file ที่ถูก write ทุกวันจะมี PII จำนวนมาก — ถ้า disk รั่ว หรือ log ถูกส่งไป ELK ที่ไม่ restrict access จะเป็น data breach |
| การแก้ไข | เปลี่ยนเป็น logging: ['error'] ใน production หรือใช้ custom logger ที่ redact PII |
| แรงที่ต้องใช้ | 0.5 วัน |
HIGH-02 · CORS origin: '*'
| ไฟล์ | centralize-api/src/main.ts:31-35 |
| ปัญหา | origin: '*' → browser script จาก domain ใดก็เรียก centralize-api ได้ |
| ผลกระทบ | XSS ที่ backoffice หรือ domain อื่นสามารถใช้ centralize-api เป็นช่องทางดึงข้อมูลออกจากระบบ |
| การแก้ไข | จำกัด origin ใน production เป็น whitelist ของ VTRC domains |
| แรงที่ต้องใช้ | 0.5 วัน |
HIGH-03 · Internal IP hardcoded ใน Swagger
| ไฟล์ | centralize-api/docs/document.config.ts:14-15, centralize-api/.env:3 |
| ปัญหา | 10.188.128.160 (Swagger addServer) + 10.188.128.58 (DB_HOST) committed |
| ผลกระทบ | เปิดเผยโครงสร้าง network ภายใน ถ้า Swagger doc หรือ .env รั่ว |
| การแก้ไข | ใช้ env var + ลบ IP ออกจาก source |
| แรงที่ต้องใช้ | 0.5 วัน |
HIGH-04 · JwtStrategy race condition + console.log debug
| ไฟล์ | centralize-api/libs/jwt/jwt.strategy.ts:9, 20-21 |
| ปัญหา | (1) this.data = payload ใน singleton → race condition ระหว่าง concurrent requests (2) console.log(' --->>>>>', payload) รั่ว JWT claims ลง stdout |
| ผลกระทบ | ถ้า guard ถูกเปิด → user A อาจได้ claims ของ user B |
| การแก้ไข | ลบ this.data field, ลบ console.log, ประกาศ JwtPayload interface |
| แรงที่ต้องใช้ | 0.5 วัน |
HIGH-05 · console.log debug statements ใน JwtAuthGuard
| ไฟล์ | centralize-api/libs/jwt/jwt-auth.guard.ts:23, 35, 40 |
| ปัญหา | this.logger.log(...) ที่ log isPublic, user, serviceName ทุก request → ข้อมูลรั่ว |
| ผลกระทบ | log ขยายเร็ว, เปิดเผย request pattern |
| การแก้ไข | ลบ debug log หรือเปลี่ยนเป็น logger.debug และตั้ง level log เป็น info ใน production |
| แรงที่ต้องใช้ | 0.5 ชั่วโมง |
HIGH-06 · Dockerfile ใช้ yarn ใน npm repo
| ไฟล์ | centralize-api/Dockerfile:4, 10 |
| ปัญหา | RUN yarn install --ignore-engines ทั้งที่ repo เป็น npm (มี package-lock.json pattern) |
| ผลกระทบ | dependency tree ใน container ไม่ตรงกับ dev → bug ที่หายาก (เช่น transitive dep ต่างกัน) + supply chain risk |
| การแก้ไข | เปลี่ยนเป็น npm ci ทุก stage |
| แรงที่ต้องใช้ | 0.5 วัน |
HIGH-07 · ไม่มี health endpoint
| ไฟล์ | ขาดหายไป — ทุกที่ |
| ปัญหา | ไม่มี /health/live หรือ /health/ready → load balancer/Kubernetes probe ต้องใช้ /api ที่รัน SQL 130 บรรทัด |
| ผลกระทบ | (1) probe ช้า → false negative (2) SQL ช้า → restart pod ทุกครั้ง → cascade failure |
| การแก้ไข | เพิ่ม HealthController ตามตัวอย่างในบท 5.7 |
| แรงที่ต้องใช้ | 0.5 วัน |
Medium (6 รายการ)
MED-01 · BadRequestException สำหรับทุก error
| ไฟล์ | ทุก service method — ดูบท 5.4 §"Error handling anti-pattern" |
| ปัญหา | throw 400 สำหรับ DB error, timeout, dead lock → client สับสน, monitoring พลาด 5xx alert |
| การแก้ไข | แยก error type — InternalServerErrorException สำหรับ DB, BadRequestException สำหรับ validation |
| แรงที่ต้องใช้ | 0.5 วัน |
MED-02 · HttpExceptionFilter จับแค่ HttpException
| ไฟล์ | centralize-api/libs/exception/http-exception.filter.ts:11 |
| ปัญหา | @Catch(HttpException) → error อื่นจะผ่านไปยัง default handler ของ NestJS ซึ่งรั่ว stack trace |
| การแก้ไข | เปลี่ยนเป็น @Catch() (catch all) |
| แรงที่ต้องใช้ | 0.5 ชั่วโมง |
MED-03 · No graceful shutdown
| ไฟล์ | centralize-api/src/main.ts — ไม่ได้เรียก app.enableShutdownHooks() |
| ปัญหา | เมื่อได้รับ SIGTERM (Kubernetes deploy) process จะตายทันที → request ที่กำลัง process ถูกตัดกลางคัน |
| ผลกระทบ | ข้อมูลอาจ inconsistent ใน caller (เช่น vtrc-api ได้ partial response) |
| การแก้ไข | เพิ่ม app.enableShutdownHooks() ใน bootstrap |
| แรงที่ต้องใช้ | 0.5 ชั่วโมง |
MED-04 · moment deprecated
| ไฟล์ | centralize-api/package.json:23 |
| ปัญหา | moment ประกาศ deprecated แล้ว (ปี 2020) — ไม่มี feature ใหม่ มีแค่ security fix |
| ผลกระทบ | ขนาดแพ็กเกจใหญ่ (250KB), ใช้ mutable object → เกิด bug ได้ |
| การแก้ไข | เปลี่ยนไปใช้ date-fns หรือ dayjs (สามารถแทนที่ได้โดยตรง) |
| แรงที่ต้องใช้ | 1 วัน |
MED-05 · Dependency outdated
| ไฟล์ | centralize-api/package.json |
| ปัญหา | NestJS 9 (ปัจจุบัน 11), TypeORM 0.3.10 (ปัจจุบัน 0.3.20+), mssql 9.1.1 (ปัจจุบัน 11), Node 16 (EOL) |
| ผลกระทบ | ไม่ได้รับ security patch ล่าสุด, ขาด feature ใหม่ |
| การแก้ไข | upgrade incremental — Node 18 LTS → NestJS 10 → mssql 11 |
| แรงที่ต้องใช้ | 3-5 วัน |
MED-06 · TypeScript strictness ปิด
| ไฟล์ | centralize-api/tsconfig.json:14-18 |
| ปัญหา | strictNullChecks: false, noImplicitAny: false, forceConsistentCasingInFileNames: false |
| ผลกระทบ | type safety อ่อนลง → bug runtime ที่ควรจะ catch ตอน compile |
| การแก้ไข | เปิดทีละ option พร้อมแก้ type error ที่เกิดขึ้น |
| แรงที่ต้องใช้ | 2-3 วัน |
Low (5 รายการ)
LOW-01 · Dead code — UserEntity, CreateCatDto, IsOnlyDate
| ไฟล์ | ดูบท 5.1 §"รวบรวม dead code" |
| ปัญหา | โค้ดที่ไม่ใช้ค้างอยู่ → ทำให้สับสนและเพิ่มภาระการดูแล |
| การแก้ไข | ลบออก |
| แรงที่ต้องใช้ | 0.5 ชั่วโมง |
LOW-02 · passport-local dependency ไม่ได้ใช้
| ไฟล์ | centralize-api/package.json:40 |
| ปัญหา | install แต่ไม่ import → ขนาดแพ็กเกจใหญ่กว่าที่ควร |
| การแก้ไข | npm uninstall passport-local |
| แรงที่ต้องใช้ | 5 นาที |
LOW-03 · คำหยาบใน code comment
| ไฟล์ | (redact — พบใน service layer) |
| ปัญหา | comment ที่มีคำหยาบใน code ส่งผลต่อวัฒนธรรมการ review และภาพลักษณ์อง่างอาชีพ |
| การแก้ไข | ลบหรือแก้ให้สุภาพ |
| แรงที่ต้องใช้ | 5 นาที |
LOW-04 · README เป็น NestJS starter default
| ไฟล์ | centralize-api/README.md |
| ปัญหา | README ไม่มีข้อมูล project-specific → onboarding ลำบาก |
| การแก้ไข | เขียนใหม่ — ระบุ purpose, architecture, env vars, deploy steps |
| แรงที่ต้องใช้ | 1 วัน |
LOW-05 · app.controller.spec.ts empty + app.e2e-spec.ts broken
| ไฟล์ | centralize-api/src/app.controller.spec.ts, centralize-api/test/app.e2e-spec.ts |
| ปัญหา | test files ที่ไม่มีประโยชน์ — spec empty, e2e ทดสอบ string 'Hello World!' ที่ไม่ match จริง |
| ผลกระทบ | npm test run ผ่านแต่ไม่ได้ตรวจอะไร |
| การแก้ไข | ลบและเขียน test จริง — อย่างน้อย smoke test สำหรับแต่ละ endpoint (ทดสอบเบื้องต้นว่า endpoint ตอบกลับได้) |
| แรงที่ต้องใช้ | 2-3 วัน |
SQL Injection — ไม่พบ
หลังจากค้นหาทั้งระบบ
| รูปแบบ | พบ? |
|---|---|
String interpolation ${variable} ใน SQL ที่เป็น user input | ❌ |
query(SELECT ... ${userInput}) | ❌ |
${body.empCode.map((_, i) => '@' + i)} | ✅ พบ แต่เป็น placeholder name ไม่ใช่ value — safe |
→ centralize-api ปลอดภัยจาก SQL injection เพราะใช้ parameterized queries ทุกที่
ลำดับความสำคัญในการแก้
Phase 1 (สัปดาห์แรก) — Critical
- CRIT-01 — เปลี่ยน DB password, ลบ
.envจาก git history - CRIT-02 — สุ่ม JWT secret ใหม่
- CRIT-03 + CRIT-04 — เพิ่ม apiKey middleware อย่างน้อย
- HIGH-01 — ปิด
logging: ['query']
Phase 2 (สัปดาห์ที่ 2-3) — High + Medium
- HIGH-04 + HIGH-05 — ลบ debug statement + แก้ race condition
- HIGH-02 — CORS whitelist
- HIGH-07 — health endpoint
- MED-01 + MED-02 + MED-03 — error handling refactor
Phase 3 (เดือนที่ 2-3) — การปรับปรุงระบบสมัยใหม่
- MED-04 + MED-05 — upgrade dependency
- MED-06 — เปิด TypeScript strict mode
- LOW-04 — เขียน README ใหม่
- LOW-05 — เขียน test จริง
สรุปประเด็นสำคัญ
- 4 Critical —
.envcommitted, JWT secret ผิดประเภท, auth bypass 100%, PII exposure ไม่มี auth - 7 High — log รั่ว PII, CORS wildcard, IP hardcoded, debug statement, Dockerfile bug, no health endpoint, race condition
- 6 Medium — error handling, filter scope, no graceful shutdown, deprecated dep, outdated dep, TS strictness ปิด
- 5 Low — dead code, dependency ไม่ได้ใช้, คำหยาบใน comment, README, test ที่ไม่ทำงาน
- SQL injection ไม่พบ — parameterized queries ใช้ถูกต้องทุกที่
- ลำดับความสำคัญ — Phase 1 ต้องแก้ภายในสัปดาห์แรกเพราะเป็น data breach risk ทันที