10.9 · Logging exposure
บทนี้ catalog สิ่งที่หลุดเข้า log — credentials, PII, และ vector ของ log injection เนื้อหาสำคัญเพราะ log มักถูก ship ไป SIEM หรือ shared storage ที่ผู้ดูแลหลายคนเข้าถึงได้
Logging stack ของ VTRC
vtrc-api
├── lib/wlog.js — custom file logger (date-partitioned)
├── lib/logging.js — Apollo Server plugin (request/response + PII redaction)
├── lib/encrypt/index.js — helper
└── 77 console.log calls — ใน routes.js ค่าเดียว
Log destinations
├── logs/info_YYYY-MM-DD.log
├── logs/error_YYYY-MM-DD.log
├── logs/verify_YYYY-MM-DD.log
├── logs/force_YYYY-MM-DD.log
└── logs/notificationLog_YYYY-MM-DD.logปัญหา — logger เองไม่มี redaction logic ทุกค่าที่ส่งเข้า log.info/log.error ถูกเขียนลง log โดยตรง
1 · Refresh token + JWT + apiKey ใน error log (Critical)
} catch (error) {
log.error(log.logTextFormat('ERROR', 'Method:renewRefreshToken', 'ERROR', error.stack, { token: token, oldRefreshToken: oldRefreshToken, context }))
responError('TOKEN_INVALID')
}🔴 Critical — ตอนเกิด error ใน refresh flow (token ผิด format, session ไม่พบ, network blip) ระบบ log
token— JWT access token เก่าoldRefreshToken— refresh token ที่ส่งเข้ามาcontext— object ที่มีuserId,device,appId, request headers (รวมapiKey)
ทั้งหมดเขียนลง logs/error_YYYY-MM-DD.log แบบ plaintext refresh token เป็น bearer credential ที่ใช้งานได้จนกว่า session row จะถูกลบ (24 ชม.)
Combined attack path
logs/error_2026-01-15.log
├── refreshToken: 'a1b2c3...'
│
├──▶ /conicle/downloadFile/../../logs/error_2026-01-15.log (path traversal, บท 9.7)
│ │
│ └──▶ อ่าน refreshToken
│ │
│ └──▶ POST /refreshToken → mint access token ใหม่
│
└──▶ ถ้า log ship ไป SIEM ที่หลายคนเข้าถึง → credential leakวิธีแกะ
// Redact ก่อน log
const safeContext = {
...context,
headers: { /* ไม่ log Authorization/apiKey */ }
}
log.error('refresh failed', {
userId: payload?.uid,
errorType: error.name,
// ไม่ log token, refreshToken, context โดยตรง
})2 · forceLoging log injection (Critical)
export const forceLoging = (errorMessage = "", method = "", param = {}) => {
log.forceLogs(log.logTextFormat("FORCE_LOGS", `Method:${method}`, "FORCE_LOGS", errorMessage, param))
return { status: true }
}🔴 Critical — mutation ที่ public (บท 9.2) รับ input อะไรก็ได้ แล้วเขียนตรงลง log.forceLogs
Exploit vectors
| Vector | วิธี |
|---|---|
| Poison audit log | ส่ง errorMessage: "LOGIN_SUCCESS user=admin" ทำให้ forensic สับสน |
| Log injection | ใส่ newline + fake log format — ถ้า downstream ใช้ regex parser มันจะสับสน |
| Storage exhaustion | ยิง JSON payload ขนาดใหญ่ซ้ำ ๆ เติม disk |
| Log4j-style | ถ้า downstream parser มี RCE ผ่าน crafted string (rare แต่เคยเกิดกับ Log4Shell) |
วิธีแกะ
- ลบ mutation ออก หรือเปลี่ยนเป็น
@auth(SUPER_ADMIN) - Sanitize input ก่อน log (escape newline, length cap)
- ใช้ structured logging ที่แยก field ชัดเจน ไม่ concat string
3 · PDF password (national ID) ใน log (Medium)
let passwordPDF = identityCard;
console.log(passwordPDF);🟢 Medium — passwordPDF derive จาก identityCard (Thai national ID 13 หลัก — PII sensitive ใช้กับ banking, government, healthcare) แล้ว console.log ออก stdout
นอกจากนี้มี pattern ที่ routes.js:78,125,232,282,360,1046,1123,1200 เป็น console.log("setpasswordPDF success") ไม่ leak password โดยตรง แต่บ่งบอกว่า code ผ่าน review โดยไม่มีใครสนใจ
Design issue — PDF password = national ID
ถึงแม้ไม่ log password การที่ PDF encryption password derive จาก identityCard ทำให้ encryption แข็งแรงแค่ขนาดที่ national ID เป็นความลับ — ซึ่ง HR, employer, หลายคนรู้ ควรใช้ random password ที่ deliver ทาง secure channel แยก
4 · Conicle debug logs รวม cookie + token (Medium)
log.info(log.logTextFormat("INFO", 'Method:authUserConicle', 'Debug', "Debug 1", { options }))
const result = await axios(options)🟢 Medium — pattern เดียวกันซ้ำที่บรรทัด 156, 158, 207, 251
options object มี Conicle credentials, session cookies, bearer tokens ทั้งหมดถูก log ไป notificationLog
วิธีแกะ
- ลบ debug log ออก
- หรือใช้
redactfunction ที่ strip sensitive fields ก่อน log
5 · console.log ใน production code (Medium · LEG-24)
routes.js — 77 calls🟢 Medium — routes.js มี console.log 77 ครั้ง ใน production code
ปัญหา
- ไม่มี log level (debug/info/warn/error)
- ไม่มี structured output (JSON)
- ปนไปกับ stdout ของ container — ยากที่จะ filter/aggregate
- บาง call มี PII (ดูข้อ 3)
วิธีแกะ
- ใช้ structured logger (
pino,winston, หรือใน Go target ใช้slog/zerolog) - ทุก log entry มี
level,timestamp,requestId,userId(non-PII) - ลบ
console.logทั้งหมด
6 · centralize-api logs full SQL ใน prod (High · CORR-5)
logging: ['error', 'query'], logging: ['error', 'query'],🟡 High — TypeORM logging config ใน prod log ทุก query — full SQL string ที่อาจมี PII (WHERE IdentityCard = '...')
นอกจาก security risk ยังมี perf cost — I/O ของ log ต่อ query
วิธีแกะ
logging: IS_PRODUCTION ? ['error'] : ['error', 'query']7 · wlog.js structure
Logger เองเขียน plain text ลง file ไม่มี redaction logic
log.logTextFormat(type, method, level, message, param)
→ return `${timestamp} [${type}] ${method} ${level} - ${message} | ${JSON.stringify(param)}`ทุกค่าใน param ถูก JSON.stringify โดยตรง — ไม่มี field filtering
สิ่งที่ควร redact โดย default
| Field | Reason |
|---|---|
password, oldPassword, newPassword | credential |
token, refreshToken, accessToken | bearer |
apiKey, Authorization | credential |
identityCard, idCard | PII (Thai national ID) |
sessionId | session identifier |
bankAccount | financial PII |
8 · PII redaction ใน Apollo plugin
lib/logging.js เป็น Apollo Server plugin ที่ (ตามชื่อ) น่าจะ redact PII ก่อน log request/response แต่ implementation จริงต้อง verify แบบ case-by-case — field ที่ชื่อ password อาจถูก redact แต่ identityCard, bankAccount อาจไม่
Plugin pattern ที่ดีคือ denylist + nested object traversal + length cap
ตารารางสรุปช่องโหว่ในบทนี้
| # | ปัญหา | Severity | File:line |
|---|---|---|---|
| 1 | refreshToken + apiKey ใน error log | 🔴 Critical | auth.js:237-240 |
| 2 | forceLoging log injection | 🔴 Critical | auth.js:329-332 |
| 3 | PDF password (national ID) log | 🟢 Medium | genSlip/index.js:17-18 |
| 4 | Conicle debug log with cookies | 🟢 Medium | conicle.js:113,156,158,207,251 |
| 5 | 77 console.log ใน routes.js | 🟢 Medium | routes.js (entire) |
| 6 | centralize-api logs full SQL ใน prod | 🟡 High | app.module.ts:34,53 |
| 7 | wlog.js ไม่มี redaction logic | 🟡 High | lib/wlog.js (entire) |
อ่านต่อ → 9.10 Supply chain + runtime