6.8 · Security debt register
บทนี้สรุป security debt ที่พบใน benefit-api ทั้งหมด จัดเรียงตาม severity พร้อม code reference, impact, และ remediation
Severity rubric
| Severity | นิยาม |
|---|---|
| Critical | exploit ได้ทันที, รับผลกระทบ wide scope (data leak / RCE) |
| High | exploit ได้ง่าย, ผลกระทบ medium-high scope |
| Medium | exploit ต้องมี precondition, ผลกระทบจำกัด |
| Low | defense in depth, hardening |
S1 — SECRET_KEY เป็น SSH public key และ commit เข้า git
Severity: Critical
Files:
.env:64— valuelibs/jwt/jwt.strategy.ts:14— ใช้เป็น JWT secretconfig/configuration.ts:9— mapping
Impact:
ค่า SECRET_KEY คือ SSH RSA public key (prefix AAAAB3NzaC1yc2EAAAADAQABAAABAQ) ที่ commit เข้า git → ใครที่เคยเข้าถึง repo สามารถ forge JWT ที่ benefit-api ยอมรับได้ ทำให้ระบบ auth ทั้งหมด bypass ได้
Remediation:
- Generate JWT secret ใหม่ (HMAC key 256-bit สุ่ม)
- หมุน secret ทุก environment (dev/uat/prod)
- ลบ secret ออกจาก git history (
git filter-branchหรือ BFG) - ย้าย secret ไปยัง secret manager (Vault, AWS Secrets Manager, GCP Secret Manager)
- เพิ่ม pre-commit hook ป้องกัน secret leak
S2 — benefit-api เขียนตรงไปยัง vtrc schema (cross-context write)
Severity: High (architecture)
Files:
app.module.ts:30-50—vtrcTypeORM connectionpayments.service.ts:568—UPDATE WDHospitalsexport-payments.service.ts:591—UPDATE WDHospitalswdHospitals.entity.ts:3—@Entity('WDHospitals', { schema: 'vtrc' })
Impact:
benefit-api และ vtrc-api เป็น writer คู่กันไปยังตารางเดียวกัน (vtrc.WDHospitals) → no single source of truth, race condition, การแก้ไข schema ในอนาคตต้อง coordinate หลายฝั่ง
Remediation:
- benefit-api ควรเรียก
vtrc-apiผ่าน GraphQL/REST ไม่ใช่เขียน DB ตรง - ระหว่างทาง ตั้ง ownership matrix ชัดเจนว่าตารางใดเป็นของ service ใด
- เพิ่ม DB-level audit trigger ที่
WDHospitalsเพื่อ track writer
ดู Volume 16 · Modernization Roadmap สำหรับกลยุทธ์ strangle
S3 — SFTP password hardcoded ใน source
Severity: Critical
Files:
exportpaymentreport.service.ts:135-145—password: '~x-:#"nTb-2k+_TaE'
Impact:
Credential SFTP (ftpfmis account) อยู่ใน git และ baked เข้า Docker image → ใครได้ source คือได้สิทธิ์ write/read file บน FMIS gateway
Remediation:
- หมุน SFTP password ทันที
- ย้ายไปยัง environment variable (
process.env.SFTP_PASSWORD) - Production: เก็บใน secret manager
- Consider key-based auth แทน password
S4 — .env commit เข้า git และ baked เข้า Docker image
Severity: Critical
Files:
.env(root of repo) — committedDockerfile:25—ADD . .copies.envinto image.gitignore—.envไม่ได้ถูก ignore
Impact:
Production credentials (MSSQL sa password h@ckm3SA?, SFTP password, JWT secret) รั่วออกไปทั้งหมดทุก environment ที่รัน image
Remediation:
- Add
.envto.gitignore - Remove
.envfrom git history - Remove
ADD .หรือเพิ่ม.dockerignoreที่ exclude.env - หมุนทุก credential ที่เคยอยู่ใน
.env - Use Docker secrets / K8s secrets / env injection at runtime
S5 — SQL injection (20+ sites)
Severity: Critical (aggregate)
Files: ดู 6.4 — Critical: 2 sites, High: 18+ sites
Impact:
เนื่องจากทุก connection ล็อกอินด้วย sa (S11) SQL injection จุดเดียว = full DB compromise: read PII, execute xp_cmdshell, lateral movement
Remediation:
- แปลง raw query ทั้งหมดเป็น parameterized (
?placeholder หรือ QueryBuilder) - เพิ่ม ESLint rule ตรวจ
entityManager.query(\...`)` pattern - Code review checklist: ห้าม template literal ใน raw query
- สั่ง pentest หลังแก้
S6 — @Public() บน mutation endpoints
Severity: Critical
Files:
wdhospitals.controller.ts:14,22—@Public() @Post('add'),@Post('update')upload.controller.ts:8—@Public() @Post('upload')manualBatch.controller.ts:10,16—@Public() @Get('pull'),@Get('push')app.controller.ts:18—@Public() @Get('ping-centrail')
Impact:
Mutation endpoints (add/update WDHospitals, trigger cron, upload files) bypass auth ทั้งหมด → anonymous attacker สร้าง/แก้ไข transaction ได้
Remediation:
- ลบ
@Public()จาก mutation endpoints ทั้งหมด - เพิ่ม role check ใน controller (
@Roles('admin')หรือ custom decorator) - Audit log ทุก mutation
S7 — SSRF ผ่าน /ping-centrail
Severity: High
Files:
app.controller.ts:18-25—@Public() @Get('ping-centrail')
@Public()
@Get('ping-centrail')
async pingCentrail(@Query('url') url: string) {
const response = await axios.get(url);
return response.data;
}Impact:
Endpoint รับ URL ตรงจาก query string แล้ว fetch ทันที → attacker สามารถ:
- Probe internal network (
http://10.x.x.x/admin) - อ่าน metadata service ของ cloud (
http://169.254.169.254/...) - ทำ DoS ผ่าน external request
Remediation:
- ลบ endpoint นี้ทันที (debug utility — ไม่ควรอยู่ใน prod)
- ถ้าจำเป็น ใช้ allowlist ของ domains ที่อนุญาต
S8 — File upload ไม่จำกัด type
Severity: High
Files:
upload.controller.ts:8—@Public() @Post('upload')
Impact:
ใครก็ upload ไฟล์เข้า /uploads/ ได้โดยไม่ login — ใช้เป็น staging ground สำหรับ malware, หรือเก็บ illegal content บน infrastructure ของสภากาชาด
Remediation:
- ลบ
@Public() - จำกัด MIME types (image/pdf เท่านั้น)
- จำกัด extension whitelist
- Scan ด้วย antivirus (ClamAV)
- Store ใน object storage (GCS/S3) ไม่ใช่ disk
S9 — body: any ใน DTOs — ไม่มี validation
Severity: Medium
Files:
studyLeave.controller.ts:25—@Body() body: any- (multiple controllers)
Impact:
ValidationPipe global ไม่ทำงานเพราะไม่มี DTO class → ทุก payload ผ่านเข้ามาโดยไม่ validate type/format/range
Remediation:
- สร้าง DTO class สำหรับทุก endpoint
- ใช้
class-validatordecorators (@IsString(),@IsInt(),@MinLength()) - เปิด
whitelist: true,forbidNonWhitelisted: trueในValidationPipe
S10 — CORS origin: '*'
Severity: Medium
Files:
main.ts:40—app.enableCors({ origin: '*' })
Impact:
Cross-origin request จาก domain ใดก็ได้ → ถ้ามี auth ที่ใช้งานได้ ก็ยังเสี่ยง credential leak ผ่าน browser
Remediation:
- ตั้ง
originเป็น allowlist (['https://vtrc.redcross.or.th', 'https://uat-vtrc...']) - ถ้าจำเป็นต้องใช้ cookie —
credentials: true+ explicit origin
S11 — ทุก connection ล็อกอินด้วย sa
Severity: High
Files:
app.module.ts:30-50— 3 connections (main,vtrc,hrmi) ทั้งหมด usersa.env:18,28,38—DB_USER_* = sa
Impact:
sa คือ MSSQL superuser — ทุก operation ทำได้รวมถึง xp_cmdshell, sp_configure, file system access ผ่าน SQL Server
Remediation:
- สร้าง dedicated DB user สำหรับ benefit-api (read/write limited tables)
- Apply principle of least privilege ที่ GRANT/DENY
- Disable
xp_cmdshellใน production
S12 — nmap ติดตั้งใน Docker image
Severity: Low
Files:
Dockerfile:18—RUN apk add --no-cache nmap ...
Impact:
nmap เป็น network scanner — ถ้า attacker ได้ RCE ใน container จะใช้เป็น pivot tool scan internal network ได้สะดวก
Remediation:
ลบ nmap ออกจาก Dockerfile — เป็น debug tool ไม่จำเป็นต่อ runtime
S13 — Swagger UI mounted publicly ใน production
Severity: Low
Files:
main.ts:35—SwaggerModule.setup('benefit-api/docs', app, document)โดยไม่มี env check
Impact:
Production expose API documentation สาธารณะ → leak endpoint topology
Remediation:
if (process.env.NODE_ENV !== 'production') {
SwaggerModule.setup(...);
}S14 — Node version skew (CI vs runtime)
Severity: Low
Files:
bitbucket-pipelines.yml:5— CI imagenode:16Dockerfile:1— runtimenode:20-alpine
Impact:
Test ใน CI อาจผ่าน แต่ runtime มี behavior ต่าง (Native modules, V8 features) → silent bug
Remediation:
align Node version — CI และ runtime ต้องตรงกัน
สรุป severity count
| Severity | Count |
|---|---|
| Critical | 6 (S1, S3, S4, S5, S6, + aggregate) |
| High | 4 (S2, S7, S8, S11) |
| Medium | 2 (S9, S10) |
| Low | 3 (S12, S13, S14) |
| Total | 15 |
Remediation priority (Top 5)
- S4 — Remove
.envfrom git, bake image fix → ปิด vector รั่วซึมหลัก - S1 — Rotate JWT secret + remove from history
- S3 — Rotate SFTP password + externalize
- S5 — Parameterize all raw SQL (จุดที่ใหญ่ที่สุด — ต้องทยอยทำ)
- S6 — Remove
@Public()จาก mutation endpoints
หลังจาก Top 5 → S11 (DB user), S7 (SSRF), S8 (upload)
ข้อแนะนำเชิงกระบวนการ
- เพิ่ม SAST (Static Application Security Testing) — SonarQube, Semgrep, CodeQL ตรวจ raw SQL pattern, hardcoded credential
- เพิ่ม dependency scan —
npm audit, Snyk, Dependabot - เพิ่ม secret scan — GitGuardian, TruffleHog ใน pre-commit และ CI
- External pentest — อย่างน้อยปีละครั้ง หลังแก้ Critical
- Threat model — ก่อนเพิ่ม feature ใหม่