Skip to content

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)
LowCode 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

  1. CRIT-01 — เปลี่ยน DB password, ลบ .env จาก git history
  2. CRIT-02 — สุ่ม JWT secret ใหม่
  3. CRIT-03 + CRIT-04 — เพิ่ม apiKey middleware อย่างน้อย
  4. HIGH-01 — ปิด logging: ['query']

Phase 2 (สัปดาห์ที่ 2-3) — High + Medium

  1. HIGH-04 + HIGH-05 — ลบ debug statement + แก้ race condition
  2. HIGH-02 — CORS whitelist
  3. HIGH-07 — health endpoint
  4. MED-01 + MED-02 + MED-03 — error handling refactor

Phase 3 (เดือนที่ 2-3) — การปรับปรุงระบบสมัยใหม่

  1. MED-04 + MED-05 — upgrade dependency
  2. MED-06 — เปิด TypeScript strict mode
  3. LOW-04 — เขียน README ใหม่
  4. LOW-05 — เขียน test จริง

สรุปประเด็นสำคัญ

  • 4 Critical.env committed, 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 ทันที