Skip to content

15.1 Critical security debt

บทนี้รวบรวม debt ด้านความปลอดภัยระดับ Critical — ความเสี่ยงที่กำลังเกิดอยู่และต้องจัดการทันที

ทั้ง 7 รายการในบทนี้เป็นเงื่อนไขที่ผู้ไม่ประสงค์ดีสามารถใช้โจมตีระบบได้โดยตรง

รายการทั้งหมด

SEC-1 · Command injection ผ่าน qpdf execSync

ข้อมูลรายละเอียด
Evidencevtrc-api/api/src/routes.js:73,120,210,260,312
ปัญหาใช้ execSync(\qpdf --encrypt ${allData.passwordPDF} ...`)) โดย passwordPDFมาจากidentityCardของผู้ใช้ (เลขบัตรประชาชน — ค่าที่ผู้ใช้ควบคุมได้) ถ้าผู้ไม่ประสงค์ดีตั้ง identityCard เป็นสตริงที่มี shell metacharacter เช่น"; rm -rf / #` คำสั่งจะถูก execute ในฐานะ process ของ Node
Impactผู้ไม่ประสงค์ดีสามารถรันคำสั่งใด ๆ บน server ได้จากการ register บัญชีด้วย identityCard ที่ถูกประดิษฐ์ ผลกระทบ = full server compromise
Mitigation ระยะสั้นเปลี่ยน execSync เป็น execFile (หรือ spawn) ที่รับ argument เป็น array ไม่ผ่าน shell — ทำได้ภายใน 1 วัน
แนวทางระยะยาวย้าย PDF generation ไป service แยกที่ไม่ใช้ shell เลย (Go + pdfcpu หรือ chromedp)

ตัวอย่างโค้ดที่เป็นปัญหา:

javascript
let cmd = `qpdf --encrypt ${allData.passwordPDF} ${allData.passwordPDF} 256 -- modify infile.pdf outfile.pdf`;
execSync(cmd);  // shell injection

ตัวอย่างโค้ดที่ปลอดภัยกว่า:

javascript
execFile('qpdf', [
  '--encrypt', passwordPDF, passwordPDF, '256', '--',
  'infile.pdf', 'outfile.pdf'
], (err, stdout, stderr) => { /* ... */ });

SEC-2 · GraphQL playground + introspection เปิดใน production

ข้อมูลรายละเอียด
Evidencevtrc-api/api/src/index.js:68-69
ปัญหาplayground: true และ introspection: true เปิดโดยไม่ตรวจ IS_PRODUCTION — production endpoint มี UI สำหรับ query และสามารถ introspect schema ทั้งหมดได้
Impactผู้ไม่ประสงค์ดีเห็น schema ทั้งหมด รู้ตำแหน่ง mutation ที่ sensitive และทดลอง query ได้ผ่าน UI ที่เปิดสาธารณะ
Mitigation ระยะสั้นตรวจ IS_PRODUCTION ก่อนเปิด playground — ทำได้ภายใน 30 นาที
แนวทางระยะยาวintrospection เป็น opt-in ในแต่ละ environment ผ่าน config ไม่ใช่ default

SEC-3 · Production device keys เก็บใน source code

ข้อมูลรายละเอียด
Evidencevtrc-api/api/src/config.js:42-148 — 7 production keys + 10 UAT keys เป็น plain UUID
ปัญหาdevice key (ส่งเป็น apiKey header ทุก request) ถูก commit ใน repo ในรูปแบบ cleartext ผู้ที่เข้าถึง repo สามารถ impersonate เป็นแอปใดก็ได้
Impactผู้ไม่ประสงค์ดีที่ได้ read access repo สามารถเรียก API ในฐานะแอปที่ถูกต้อง ข้ามการตรวจสอบประเภทแอป
Mitigation ระยะสั้นย้าย device key ไป environment variable (เริ่มต้น) หรือ secret manager
แนวทางระยะยาวเก็บ key ในรูปแบบ hash (Argon2id) ใน database เปรียบเทียบแบบ constant time และเลิกใช้ header แบบ static — ใช้ claim ใน signed JWT แทน

SEC-4 · JWT_SECRET เป็นค่า default 'secret'

ข้อมูลรายละเอียด
Evidencevtrc-api/api/src/config.js:12
ปัญหา`JWT_SECRET = process.env.JWT_SECRET
Impactauthentication bypass ทั้งระบบ — ผู้ไม่ประสงค์ดีสามารถสร้าง JWT ที่เป็น admin ได้โดยไม่ต้อง login
Mitigation ระยะสั้นเปลี่ยนให้ service refuse to start ถ้า JWT_SECRET ไม่ถูกตั้ง — ทำได้ภายใน 30 นาที
แนวทางระยะยาวใช้ key ที่โหลดจาก KMS หมุนเวียน key เป็นระยะ

SEC-5 · centralize-api CORS origin '*'

ข้อมูลรายละเอียด
Evidencecentralize-api/src/main.ts:31-35
ปัญหาCORS origin: '*' + allowedHeaders: '*' บน HR-data service เว็บไซต์ใด ๆ ก็สามารถเรียก central HR API แบบ cross-origin ได้
Impactผู้ไม่ประสงค์ดีสร้างเว็บที่หลอกให้ผู้ใช้เข้า แล้วใช้ session ของผู้ใช้เรียก HR API จาก browser ได้
Mitigation ระยะสั้นกำหนด allow-list ของ origin ที่ชัดเจน (domain ของ frontend ทั้งสองตัว) — ทำได้ภายใน 30 นาที
แนวทางระยะยาวใช้ CORS middleware ที่มี whitelist ในทุก service

SEC-11 · GCP service account key commit ใน repo

ข้อมูลรายละเอียด
Evidencevtrc-api/vtrc-gcp-sa-key.json (ไฟล์ปรากฏใน repo — ตาม workspace rule ห้าม log หรือ paste)
ปัญหาGCP service account key ถูก commit ใน repo ผู้ที่เข้าถึง repo สามารถใช้ key นี้เข้าถึงทรัพยากร GCP ทั้งหมดที่ SA มีสิทธิ์
Impactการเข้าถึง GCP project โดยไม่ได้รับอนุญาต อาจรวมถึงการ push image ปลอม, เข้าถึง bucket, หรือใช้บริการอื่น ๆ
Mitigation ระยะสั้นหมุนเวียน key ทันที ลบไฟล์ออกจาก repo (และ git history ถ้าจำเป็น)
แนวทางระยะยาวใช้ Workload Identity หรือ short-lived token แทน long-lived service account key

PERF-7 · phpMyAdmin เปิดสู่สาธารณะบน port 8093

ข้อมูลรายละเอียด
Evidencevtrc-api/docker-compose.yml:35-46
ปัญหาphpMyAdmin container เปิด port 8093 สู่ internet โดย PMA_HOST ชี้ไป database host จริง — เป็น database admin UI ที่เข้าถึงได้จาก internet
Impactผู้ไม่ประสงค์ดีสามารถ brute-force password หรือ exploit CVE ของ phpMyAdmin เพื่อเข้าถึง database ทั้งหมด
Mitigation ระยะสั้นลบ phpMyAdmin ออกจาก production compose หรือจำกัดด้วย IP allow-list ที่ firewall — ทำได้ภายใน 1 ชั่วโมง
แนวทางระยะยาวใช้ bastion host + SSH tunnel สำหรับ database admin ไม่มี database UI เปิดสาธารณะ

สรุป

IDรายการEffort ระยะสั้น
SEC-1qpdf command injection1 วัน
SEC-2playground + introspection prod30 นาที
SEC-3device keys ใน repo2-3 วัน (ย้าย + rotate)
SEC-4JWT_SECRET default30 นาที
SEC-5centralize CORS *30 นาที
SEC-11GCP SA key1-2 วัน (rotate + cleanup history)
PERF-7phpMyAdmin เปิดสาธารณะ1 ชั่วโมง

ทั้ง 7 รายการนี้เป็นเป้าหมายของ Wave 0 (stop-the-bleed) ในบท 14.10 — แก้ไขได้ภายใน 1-2 สัปดาห์ โดยไม่ต้องเปลี่ยน architecture