บทที่ 12 · Part 2 — Identity & Web Security
Browser, File & Content Security
XSS, CSRF, CSP, Trusted Types, clickjacking, open redirect, browser storage และ secure file pipeline สำหรับเอกสาร KYC
ข้อความ merchant ที่ถูกเก็บใน database อาจดูเป็น data ธรรมดา แต่เมื่อ operations portal นำไปใส่
innerHTML ข้อความนั้นกลายเป็น code ที่รันด้วย session ของ operator ส่วนไฟล์ KYC ที่ตรวจเพียง
นามสกุล .jpg อาจเป็น document/parser payload ที่โจมตี worker หลังบ้านแทน browser
Learning Outcomes
- แยก Reflected, Stored และ DOM-based XSS จาก source/sink ได้
- ใช้ safe rendering, contextual encoding, sanitization, CSP และ Trusted Types ตามหน้าที่ได้
- ป้องกัน CSRF, clickjacking และ open redirect แบบหลายชั้นได้
- เลือก browser storage และ security headers ตาม data/credential boundary ได้
- ออกแบบ upload/download pipeline ที่ตรวจ type, limit, isolate, authorize และ retain อย่างปลอดภัยได้
XSS คือ Data กลายเป็น Active Content
| Type | Source → Execution |
|---|---|
| Reflected | request input ถูกใส่ response แล้ว execute ทันที |
| Stored | payload ถูกเก็บ แล้ว execute เมื่อผู้ใช้อื่นเปิดหน้า |
| DOM-based | client-side code อ่าน source แล้วส่งเข้า dangerous DOM sink |
ชื่อ type บอกเส้นทาง แต่ defense ต้องดู output context และ sink จริง
Safe และ Dangerous Sinks
// Safe for plain text: browser does not parse value as HTML.
statusElement.textContent = untrustedStatus
// Dangerous: value becomes HTML and may execute active content.
statusElement.innerHTML = untrustedStatus
framework template มัก escape text โดย default แต่ escape ถูก bypass เมื่อใช้ raw HTML API, string-built URL/event handler, third-party widget หรือ DOM API โดยตรง
Output Encoding ต้องตรง Context
| Context | แนวทางหลัก |
|---|---|
| HTML text | framework auto-escape / HTML entity encoding |
| Attribute | quoted attribute + attribute encoding + allowlist attribute |
| URL | parse/allowlist scheme-host + encode component |
| JavaScript | หลีกเลี่ยง inline interpolation; serialize data safely |
| CSS | ไม่รับ untrusted CSS; ใช้ property/value allowlist |
HTML encoding ค่าแล้วนำไปใส่ JavaScript context ยังไม่ปลอดภัย หลีกเลี่ยงการประกอบ code/string และใช้ structured API
Sanitization ใช้เมื่อจำเป็นต้องรับ HTML
บาง feature ต้องรองรับ rich text Sanitization ต่างจาก encoding: sanitizer parse HTML แล้วลบ element/attribute/protocol ที่ไม่อนุญาต
หลักปฏิบัติ:
- ใช้ maintained sanitizer ที่เหมาะกับ browser/server context
- กำหนด allowlist เล็กและไม่มี script/event/style/unsafe URL
- sanitize หลัง canonical parse และก่อน sink
- เก็บ original แยกจาก sanitized version ตาม privacy/forensic policy
- re-sanitize เมื่อ policy/library เปลี่ยนถ้าข้อมูลเก่าถูก render
- test mutation, SVG/MathML และ browser parsing edge cases ตาม library scope
ห้ามแก้ HTML ด้วย regex และห้าม modify sanitized output ด้วย string concatenation ที่ทำให้ unsafe อีกครั้ง
CSP เป็น Defense in Depth
Content Security Policy จำกัดแหล่งและรูปแบบ content/execution CSP ที่ใช้ nonce/hash สามารถลด ผลของ injection บางชนิด แต่ไม่แทน safe rendering
ตัวอย่าง policy เริ่มต้นที่ต้องปรับตาม application:
Content-Security-Policy: default-src 'none'; script-src 'nonce-random-per-response' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; connect-src 'self' https://api.example.com; base-uri 'none'; object-src 'none'; frame-ancestors 'none'
- nonce ต้องสุ่มต่อ response และ attacker คาดเดาไม่ได้
- ห้ามใส่ nonce ให้ script ที่ attacker ควบคุม body/source ได้
unsafe-inlineทำให้ script policy อ่อนลง- host allowlist กว้างอาจมี JSONP/user content/bypass
- เริ่มด้วย report-only เก็บ violation และปรับ ก่อน enforce
- report endpoint ต้อง rate-limit/redact เพราะ report มี URL/data ได้
CSP Report ไม่ใช่หลักฐานว่าไม่มี XSS
browser coverage, extension, blocked report และ code path ที่ไม่ถูกใช้ทำให้ telemetry ไม่ครบ CSP ต้องทำงานร่วมกับ safe sink, encoding, sanitization และ test
Trusted Types ลด DOM XSS Sink
Trusted Types ช่วยบังคับ dangerous DOM sinks ให้รับ typed value ที่สร้างผ่าน policy แทน string ทั่วไป เหมาะกับ application ขนาดใหญ่ที่ต้องการ migrate DOM code
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-policy
policy factory ต้องใช้ sanitizer/validation จริง ไม่สร้าง policy ที่คืน input ตรง ๆ มิฉะนั้นเพียงย้าย ช่องโหว่ไปจุดกลาง และต้องตรวจ browser support/rollout
CSRF: Browser ส่ง Credential ให้อัตโนมัติ
เมื่อใช้ cookie session state-changing request ต้องมี CSRF control ตาม architecture:
- synchronizer/double-submit token ที่ออกแบบถูก
- ตรวจ
Originและ Fetch Metadata เมื่อเหมาะสม SameSiteเป็น defense in depth- ไม่ใช้ GET ทำ side effect
- fresh authorization/confirmation สำหรับ action สำคัญ
XSS อาจสั่ง request จาก origin จริงและอ่าน CSRF token ได้ จึงต้องแก้ทั้ง 2 ปัญหา ดู browser/origin model ที่บท Web Security Model
Clickjacking และ Framing
attacker อาจวางหน้าธุรกรรมโปร่งใสทับ UI หลอกให้คลิก ป้องกันด้วย:
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
ถ้าต้อง embed ให้ allowlist origin ที่จำเป็นและออกแบบ postMessage/transaction confirmation
อย่างปลอดภัย frame-ancestors เป็น control หลักสมัยใหม่ ส่วน X-Frame-Options ช่วย compatibility
Open Redirect และ Navigation
parameter เช่น return_to ที่รับ arbitrary URL ช่วย phishing และอาจรั่ว authorization code/token
แนวทาง:
- ใช้ relative path หรือ server-side destination ID
- parse URL แล้ว exact allowlist scheme/host/port/path ตาม use case
- reject userinfo, alternate scheme, protocol-relative และ ambiguous encoding
- OAuth redirect URI ต้อง exact match ตาม protocol
- อย่าแนบ credential/token ไป destination ที่ client เลือก
warning page อย่างเดียวอาจถูกผู้ใช้คลิกผ่านและไม่แก้ token leakage
Browser Storage และ Client Data
| Storage | ความเสี่ยง/แนวใช้ |
|---|---|
| HttpOnly cookie | JS อ่านไม่ได้ แต่ browser แนบอัตโนมัติและต้องทำ CSRF control |
| Local/Session Storage | JS/XSS อ่านได้; ไม่มี HttpOnly; ไม่เหมาะ long-lived session secret |
| IndexedDB | เก็บได้มากและ JS อ่านได้; ต้อง classify/expiry/clear |
| In-memory | ลด persistence แต่ยังถูก XSS ใน runtime และหายเมื่อ reload |
| Cache/Service Worker | offline data/response อาจคงหลัง logout และแชร์ผิด scope |
frontend ไม่ควรเก็บ KYC, statement หรือ token เกินที่ UX ต้องใช้ logout/account switch ต้อง clear
client cache ที่เกี่ยวข้อง และ Cache-Control ฝั่ง HTTP ต้องตรง data class
Security Headers ที่ควรพิจารณา
Content-Security-Policy: <application-specific policy>
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cross-Origin-Opener-Policy: same-origin
ค่าไม่ควรถูก copy ทั้งชุดโดยไม่ทดสอบ OAuth, embedded content, file download, customer support และ third-party integration Security header เป็น policy ของ browser ไม่แทน backend control
File Upload เป็น Pipeline ไม่ใช่ If Statement
แต่ละชั้นแก้คนละ threat:
- authorize uploader และ business object
- allowlist extension/type ตาม use case
- ไม่เชื่อ
Content-Type; ตรวจ signature/structure ด้วย parser ที่จำกัด - generate filename/object ID ไม่ใช้ชื่อ client เป็น storage path
- จำกัด bytes, dimensions, pages, entries, recursion และ decompressed size
- quarantine ก่อน scan และไม่ให้ execute/serve active content
- parser/scan รัน isolated least privilege พร้อม timeout/resource limit
- เก็บนอก webroot หรือคนละ host/origin
- authorized retrieval + audit + retention/deletion
ไม่มี antivirus หรือ signature check ตัวเดียวที่รับประกันว่าไฟล์ปลอดภัย
KYC Document Processing
เอกสาร KYC มีทั้ง PII sensitivity และ parser risk:
| Stage | Control |
|---|---|
| Upload | authenticated purpose, consent/policy, size/type limits |
| Quarantine | encrypted, restricted, random ID, no public serving |
| Processing | isolated worker, minimal network/role, CPU/memory/time limits |
| Review | masked/minimized display, role/case authorization, watermark/audit ตาม policy |
| Storage | key/access boundary, retention, backup และ deletion workflow |
| Export | approval, recipient/purpose, encryption และ traceability |
Retention และการแสดงข้อมูลต้องให้คนตัดสิน
ระยะเวลาเก็บ, การ mask, ผู้ตรวจที่เข้าถึงได้ และการส่งต่อเอกสารเป็น policy/compliance decision ระบบควร enforce และสร้าง evidence แต่ไม่ควรเลือกค่าจากตัวอย่าง
Download อย่างปลอดภัย
- authorize object ทุก request และใช้ short-lived scoped URL หากเหมาะสม
- set server-generated filename และ safe
Content-Disposition - set accurate
Content-Type+X-Content-Type-Options: nosniff - active/untrusted content ควร serve จาก isolated origin และเป็น attachment
- ห้าม token ใน query ที่รั่วผ่าน log/referrer โดยไม่มี compensating design
- revoke/delete ต้องครอบคลุม CDN/cache/derived thumbnail ตาม lifecycle
Testing Checklist
- inventory source/sink รวม raw HTML, URL, DOM, template และ third-party widget
- rendering ใช้ safe sink/contextual encoding; raw HTML ผ่าน maintained sanitizer
- CSP nonce/hash และ Trusted Types policy ไม่คืน input ตรง
- CSRF มี token/origin/SameSite ตาม credential model
- framing ถูกจำกัดด้วย
frame-ancestors - redirect/navigation ใช้ relative หรือ exact allowlist และไม่พา credential ออก
- browser storage/cache ถูก classify, expire และ clear เมื่อ logout/switch account
- upload ตรวจ auth, type, size/structure, quarantine, isolate และ scan
- KYC access/retention/export มี policy owner และ audit
- download authorize, set type/disposition และ isolate active content
สรุป
Browser security ต้องควบคุมจุดที่ data กลายเป็น code, navigation หรือ credentialed request ส่วน file security ต้องมองทั้ง pipeline ตั้งแต่ upload ถึง delete CSP, scanner และ extension check เป็น defense in depth ไม่มีตัวใดแทน safe rendering, isolation และ authorization ได้