4 · Auth mechanism failures
ช่องโหว่ประเภท “มี auth ในชื่อ แต่ไม่มีในความจริง” — Deep Redo แยก backdoor/bypass, @Public() / commented guards, และ broken verify design (ไม่ใช่แค่ secret หลุด)
English: Auth failures = intentional bypasses + accidental public surfaces + broken verification logic. Distinct from secret leakage (Chapter 3), though they compound.
กลับ Blast radius · ถัดไป 90-day plan
4.1 Taxonomy
| Class | Definition | Example IDs |
|---|---|---|
| A · Intentional backdoor | Hardcoded credentials or magic tokens in source | SEC-2WAYAPI-01, SEC-2WAYVTRC-01, SEC-CU-04, SEC-RCWEB-03 |
| B · Guard present but skipped | Global JWT + @Public() / commented middleware | SEC-2WAYAPI-04, CRIT-03, SEC-MTVTRC-05, SEC-BENEFIT-03 |
| C · Verify broken / incomplete | Decode without verify; ignore expiry; wrong arity; no role | SEC-WORKFLOW-01, SEC-2WAYAPI-05, SEC-MTVTRC-06, SEC-AIRECRUIT-02, CORR-SSO-01 |
| D · Client-side only “auth” | Token in URL/localStorage; soft checks | SEC-VTRC-RC-02, SEC-VTRC-WEB-01, SEC-2WAYAPI soft Bearer on lists |
4.2 Class A — Intentional backdoors / bypass strings
A1 · SEC-2WAYAPI-01 — i3admin → JWT admin 48h
- Where:
2way-api/src/modules/auth/auth.service.ts (branch: prod):184-240 - Route:
POST …/auth/is@Public()(auth.controller.ts:13-28) - Behavior: Hardcoded username/password; no
NODE_ENVgate; issues real JWT withSECRET_JWT_TWOWAY;mockProfile.level = 1skips org filters - Failure mode: Authentication replaced by a shared password in git
- Fix: Delete block; audit
adm9999; rotate TWOWAY secret (Runbook R02)
A2 · SEC-2WAYVTRC-01 / 01b — "testnaja"
- Where:
checkbypasstoken.ts:24;checkByPassTokenLine.ts:30(prod) - Behavior: Middleware returns success when token equals static string — skips JWT verify for admin and LINE LIFF surfaces
- Failure mode: Knowledge of source code = full auth bypass
- Fix: Remove both conditions (Runbook R03)
A3 · SEC-CU-04 — /auth/signinbypass
- Where:
cu-central-api/main-api/src/routes.js - Behavior: Passwordless signin still mounted
- Failure mode: Identity without credential proof
- Fix: Remove from UAT/prod (Runbook R04)
A4 · SEC-RCWEB-03 — OTP 111111
- Where:
recruitment-web/src/components/header/index.js:230-231(prod) - Behavior: Client accepts constant OTP in production bundle
- Failure mode: Second factor is theater
- Fix: Strip from prod builds; server must reject constant OTP regardless of client
A5 · Related soft bypasses (High but auth-shaped)
| ID | Behavior | Evidence |
|---|---|---|
SEC-RECRUIT-04 | reCAPTCHA bypass when token omitted | recruit scorecard High |
SEC-RECRUIT-05 | x-refresh-token header bypasses Passport validate | recruit scorecard High |
SEC-CU-05 | ignoreAuth with apiKey only | directives/auth.js |
4.3 Class B — Guards skipped (@Public() / comments)
B1 · NestJS global guard + @Public() epidemic
Pattern from 2way-api scorecard:
// libs/jwt/jwt-auth.guard.ts:15-24
if (isPublic) {
return true;
}
return super.canActivate(context);| ID | Repo | What is public | Evidence |
|---|---|---|---|
SEC-2WAYAPI-04 | 2way-api | Mutations + LINE push | announces.controller.ts:45,56,79,85; complaints.controller.ts:13-114; line.controller.ts:13-34 |
SEC-2WAYLINE-01 | 2way-line-service | All LineController | line.controller.ts:13,19,25,31 |
SEC-BENEFIT-03 | benefit-api | Medical claim CRUD | wdhospitals.controller.ts:24-40 |
SEC-BENEFIT-04 | benefit-api | Batch HRMI import/update | manualBatch.controller.ts:13-34 |
SEC-BENEFIT-05 | benefit-api | Upload/download | upload.controller.ts:13-22 |
SEC-PMS-01 | pms-api | Employee salary | budget.controller.ts:49-53 (uat) |
SEC-RECRUIT-03 | recruitment-api | Blacklist import | Vol 12.5 |
SEC-COMMON-01 | vtrc-common | File/address/bank controllers | scorecard Critical |
CRIT-03 | centralize-api | 100% of routes | Vol 12.3 |
ไทย: การมี JwtAuthGuard global ทำให้ผู้พัฒนาเชื่อว่า “มี auth แล้ว” — Deep Redo แสดงว่า mutation สำคัญถูก override เป็น public
B2 · Express middleware commented out
| ID | Repo | Evidence | Effect |
|---|---|---|---|
SEC-MTVTRC-05 | meeting-vtrc-api | app.ts:104-120 entire /meeting/secure/* chain commented | Almost all “secure” API open |
SEC-2WAYVTRC-02 | 2way-vtrc-api | Auth on getProfile commented | PII by lineID |
SEC-2WAYAPI-08 | 2way-api | EmpCodeMiddleware next() only :33-49 | Employees open proxy |
SEC-VTRC-API-03 | vtrc-api | /webdeployment no middleware | Shell trigger |
B3 · Path name lies
| Path | Reality | ID |
|---|---|---|
/meeting/secure/* | No global auth | SEC-MTVTRC-05 |
/meeting/secure/health-check/email | Sends real email, no auth | SEC-MTVTRC-03 · health-check.ts:9-17 |
@Public() “for testing” left in prod | Write/PII open | benefit/pms/recruit cluster |
4.4 Class C — Broken verification design
C1 · SEC-WORKFLOW-01 — decode without verify + role from DB name
- Evidence:
workflow-api/src/main.ts (uat):9-82— nouseGlobalGuards - Evidence:
jwt-actor.util.ts:1-4,33-47—decodeJwtPayloadUnsafesplits JWT, base64-decodes payload, nojwt.verify - Evidence:
ipd-user.util.ts:20-65—inferHospitalUserRole(sourceDB)maps DB name → SOMDEJ/SABC/CHULA - Swagger admits signature not verified (
main.ts:42per scorecard) - Failure mode: Attacker crafts Bearer with chosen
sourceDB→ hospital role impersonation - Fix: Real JWT verify; server-side role source (Runbook R18)
C2 · SEC-2WAYAPI-05 — ignoreExpiration: true
- Evidence:
auth.service.ts:31-40(also ~290, ~339-357) - Contrast:
JwtStrategyusesignoreExpiration: false(libs/jwt/jwt.strategy.ts:13-14) - Failure mode: Expired VTRC/other tokens can be exchanged for fresh two-way tokens via redirect/verify paths
- Fix:
ignoreExpiration: false; proper refresh policy
C3 · SEC-MTVTRC-06 — bypass middleware cannot verify
- Evidence:
checkbypass.ts:38-78builds GraphQL query but nevervtrcConnector.post - Evidence:
jwtdecode.ts:12-19requires(token, secret)but called with one arg - Evidence: Missing Bearer still
next()(checkbypass.ts:28-30) - Failure mode: Re-enabling commented global guard without this fix recreates a false sense of security
- Fix: Mirror
checkbypasswhenredirect.ts; reject empty Bearer
C4 · SEC-AIRECRUIT-02 — signature only
- Evidence:
ai-recruitment-api/middleware/verifyJWT.js:7-40(prod) - Failure mode: Combined with shared
SECRET_KEY(Chapter 3), any forged Domain JWT can burn billable AI - Fix: Role/scope + rate limit + separate secret
C5 · CORR-SSO-01 — JWT issuance commented; JwtService not injected
- Evidence:
sso-api/src/app.service.ts:27,143-191,220-223;app.controller.ts:36-51 - Severity in scorecard: High (not Critical) — but auth flow is broken:
signIndoes not issue JWT; endpoints behindJwtAuthGuard(signOut,verifySession,request2FA,verify2FA) cannot work as designed after signIn - Failure mode: Design says 2FA exists; implementation cannot complete session lifecycle
- Fix: Decide IdP of record; inject
JwtServiceor stop advertising guarded 2FA until fixed (Vol 12.5 ROI item 5)
C6 · SEC-2WAYVTRC-09 — passport done(null, true) always
- Evidence:
middleware/jwtpassport.ts:9-12 - Failure mode: Any JWT signed with
SECRETpasses without binding user - Fix: Validate claims / load user; reject unknown subjects
C7 · SEC-VTRC-API-02 / SEC-CU-01 / SEC-2WAYAPI-02 — defaults that defeat verify
If verification uses a known default ('secret', empty string, or committed fallback), “verify” still accepts attacker-signed tokens. Treat as auth failure class C even when coded as jwt.verify.
C8 · Chat webhook / WebSocket
| ID | Failure | Evidence |
|---|---|---|
SEC-CHAT-02 | WS chat no guard; trust fromline from client | chat.gateway.ts:19-26,49-67 |
SEC-CHAT-03 | LINE webhook @Public without signature | app.controller.ts:18-22 |
4.5 Class D — Client-side session theater
| ID | Mechanism | Why server auth still needed |
|---|---|---|
SEC-VTRC-RC-02 | /pathLogin/:token/:refreshToken | Tokens in history/logs/Referer |
SEC-BONEW-01 | Cross-origin URL tokens | Same + extra hop |
SEC-VTRC-RC-05 / SEC-VTRC-WEB-01 | localStorage | XSS (SEC-VTRC-RC-04, SEC-VTRC-WEB-02) steals session |
SEC-2WAYMTBO-01 / 02 | localStorage + client route guard only | Bundle still ships “protected” pages |
| Soft Bearer on 2way announce list | Optional Authorization + isMobile | Route remains @Public() — QUAL-2WAYAPI-07 |
ไทย: การเก็บ token ใน localStorage ไม่ใช่ “bug เล็ก” เมื่อคู่กับ XSS ไม่มี CSP — เป็น session theft pipeline
4.6 Auth failure interaction map
Class A backdoor ──issues──► real JWT (Class C still verifies signature)
│
└── uses Cluster C/A secrets (Chapter 3)
Class B @Public ──skips──► Class C entirely (no token needed)
Class C ignoreExpiration ──extends──► lifetime of Class A tokens
Class D XSS ──steals──► tokens issued after “successful” Class C verify
CORR-SSO-01 ──breaks──► intended 2FA while other services still trust SSO-shaped flows4.7 Detection / audit queries for operators
อย่าใส่ secret ในคำสั่ง — ใช้เพื่อหา pattern ในซอร์ส บน canonical branch:
# Backdoors / magic strings (from known IDs)
rg -n "i3admin|testnaja|signinbypass|111111" --glob '!**/node_modules/**'
# Nest public surface
rg -n "@Public\\(" --glob '**/*controller*.ts'
# Commented secure mounts (meeting)
rg -n "meeting/secure" meeting-vtrc-api/app.ts
# ignoreExpiration
rg -n "ignoreExpiration:\\s*true"
# Unsafe JWT decode helper names
rg -n "decodeJwtPayloadUnsafe|ignoreAuth"Access-log hunts suggested by scorecards:
| Hunt | Why |
|---|---|
empCode=adm9999 / username i3admin | SEC-2WAYAPI-01 |
| Requests with weird Bearer equal to bypass strings | SEC-2WAYVTRC-01 |
Unauthenticated hits to /budget/getEmployeeSalary | SEC-PMS-01 |
/wdHospitals/add without Authorization | SEC-BENEFIT-03 |
/auth/signinbypass | SEC-CU-04 |
4.8 Acceptance criteria for “auth fixed” (platform)
Auth is not fixed until all are true for a service:
- No Class A strings/routes on prod branch
- No Class B
@Public()on write/PII (inventory signed off) - Class C: verify signature + expiry + server-side authorization
- Secrets not defaulted (Chapter 3)
- Frontend does not put tokens in URLs (Class D)
Meeting-specific: SEC-MTVTRC-05 and SEC-MTVTRC-06 both closed — either alone is insufficient.
4.9 Cross-links to deep scorecards (samples)
| Service | Scorecard path | Auth dimension status (from scorecard) |
|---|---|---|
| benefit-api | volume-06-domain-microservices/benefit-api/06-scorecard.md | Auth pipeline 🔴 — @Public medical/batch/upload |
| 2way-api | volume-05-2way-platform/2way-api/04-scorecard.md | Auth pipeline แดง — backdoor + @Public + ignoreExpiration |
| meeting-vtrc-api | volume-07-meeting-platform/meeting-vtrc-api/04-scorecard.md | Global auth commented + bypass broken |
| vtrc-api | volume-03-legacy-core/vtrc-api/10-scorecard.md | Command injection + unauth deploy + default JWT |
| sso-api | volume-06-domain-microservices/sso-api/04-scorecard.md | CORR-SSO-01 High — issuance dead |