บทที่ 11 · Part 2 — Identity & Web Security

Injection & Input Security

Validation, canonicalization, parameterization และ contextual encoding สำหรับ SQL/NoSQL, command, path, template และ deserialization

input ที่ผ่าน JSON schema อาจยังทำให้โอนเงินติดลบ, SQL query เปลี่ยนความหมาย หรือ path หลุดจาก directory ที่อนุญาต เพราะ validation, parameterization, authorization และ business invariant แก้ปัญหาคนละชั้น

Learning Outcomes

  • แยก syntactic validation, semantic validation, canonicalization และ encoding ได้
  • ป้องกัน SQL/NoSQL/command/template injection ด้วย API ที่แยก code จาก data ได้
  • ออกแบบ path/file handling ที่ไม่พึ่ง string prefix ได้
  • ลด unsafe deserialization และ resource-exhaustion risk ได้
  • สร้าง negative/fuzz tests ตาม source-to-sink data flow ได้

Input ทุก Boundary ถือว่า Untrusted

untrusted input ไม่ได้มีเฉพาะ form จาก Internet:

  • HTTP path/query/header/body/cookie
  • file, image, document และ archive
  • webhook และ third-party API response
  • queue/event ที่ producer อื่นสร้าง
  • database record เก่าที่เคยรับโดย validation คนละรุ่น
  • cache, object storage และ spreadsheet import
  • admin/support tool
  • environment/config ที่ attacker หรือผิดพลาดเปลี่ยนได้

ตรวจ input เมื่อข้าม trust boundary และตรวจ business invariant อีกครั้งใกล้ side effect

Validation มีหลายหน้าที่

Layerคำถามตัวอย่าง amount
Parseแปลงเป็น type ได้หรือไม่JSON number เป็น integer
Syntacticรูปแบบ/length/range พื้นฐานถูกไหมหน่วยสตางค์, ไม่มีทศนิยม
Semanticมีความหมายในโดเมนไหมamount > 0, currency รองรับ
Authorizationactor ใช้ค่านี้ได้ไหมwallet ต้นทางเป็นของ subject
State invariantสถานะปัจจุบันอนุญาตไหมavailable balance และ transaction version

frontend validation ช่วย UX แต่ backend ต้องตรวจทั้งหมด เพราะ client ถูกแก้หรือข้ามได้

Allowlist ดีกว่า Denylist

เมื่อ domain จำกัดได้ ให้ประกาศค่าที่อนุญาต เช่น currency, sort field, state transition หรือ file type denylist ของอักขระอันตรายมักพลาด encoding, dialect และ syntax ใหม่

var allowedSort = map[string]string{
	"created_at": "created_at",
	"amount":     "amount_satang",
}

column, ok := allowedSort[input.Sort]
if !ok {
	return errors.New("unsupported sort field")
}

allowlist identifier ใช้ได้เพราะ SQL parameter ไม่สามารถแทน table/column keyword

Canonicalize ก่อนตัดสินใจ

ข้อมูล 1 ค่าอาจเขียนได้หลายแบบจาก URL encoding, Unicode, case, path separators หรือ IP notation ถ้า validation กับ consumer เห็นคนละ representation จะเกิด bypass

หลักสำคัญ:

  • decode/normalize ตาม protocol ครั้งเดียวในชั้นที่กำหนด
  • reject malformed/ambiguous encoding แทนการเดา
  • validation และ use ต้องอ้าง canonical value เดียวกัน
  • กำหนด Unicode normalization/case policy สำหรับ identifier
  • อย่า normalize secret เช่น password โดยไม่มี protocol requirement
  • ระวัง proxy/framework decode ซ้ำก่อนถึง application

canonicalization ไม่แทน authorization และอาจเปลี่ยนความหมายถ้าใช้ผิด locale/context

SQL Injection: แยก Code จาก Data

รูปแบบอันตราย:

query := "SELECT id, balance FROM wallets WHERE owner = '" + owner + "'"
rows, err := db.QueryContext(ctx, query)

ใช้ parameterized query:

const query = `
SELECT id, balance_satang
FROM wallets
WHERE owner_id = $1 AND tenant_id = $2`

rows, err := db.QueryContext(ctx, query, ownerID, tenantID)

parameterization ป้องกัน input เปลี่ยน syntax ของ value แต่ยังต้อง:

  • allowlist dynamic identifier/order direction
  • authorize tenant/owner
  • จำกัด result/timeout/resource
  • ไม่ประกอบ query fragment จาก client
  • ตรวจ stored procedure/ORM raw query และ migration tool

Escape String ไม่ใช่ Universal Defense

escaping ขึ้นกับ database, encoding, connection mode และ context การใช้ parameterized API ที่ driver รองรับมี boundary ชัดกว่า manual escaping

NoSQL และ Query Object Injection

API ที่รับ JSON object ไปเป็น query โดยตรงอาจให้ attacker ส่ง operator:

{
  "email": { "$ne": null }
}

อย่าส่ง arbitrary client object เข้า query builder ให้ parse field เป็น expected scalar type, สร้าง query object ฝั่ง server และ allowlist filter/operator/sort พร้อม authorization

GraphQL ก็ไม่ได้ปลอด injection โดยตัว format resolver ที่สร้าง SQL/command/string ยังต้องใช้ parameterized API และ query depth/cost limits แยกต่างหาก

OS Command Injection

หลีกเลี่ยง shell เมื่อมี library/API ทำงานนั้นได้ หากต้องเรียก process ให้แยก executable กับ arguments:

cmd := exec.CommandContext(ctx, "/usr/bin/convert", "--", inputPath, outputPath)
cmd.Env = []string{"PATH=/usr/bin:/bin"}
cmd.Dir = workDir
output, err := cmd.CombinedOutput()

แม้ไม่ผ่าน shell ยังต้องระวัง:

  • option injection เช่น filename ขึ้นต้น -
  • executable/path search และ environment
  • file/path traversal และ symlink
  • CPU/memory/time/output size
  • inherited file descriptors/credentials
  • parser vulnerability ของโปรแกรมลูก

run ด้วย least privilege ใน isolated workspace พร้อม timeout/resource limit อย่า log raw command ที่มี secret

Path Traversal และ File Boundary

การตรวจว่า string ไม่มี ../ ไม่พอเพราะ encoding, absolute path, separator และ symlink

แนวทาง:

  1. ใช้ server-generated opaque file ID แทน client path เมื่อทำได้
  2. map ID → metadata/path ภายใน trusted storage
  3. resolve/canonicalize ภายใต้ allowed root ด้วย platform API
  4. reject absolute path, traversal และ unexpected type
  5. จัดการ symlink/TOCTOU ด้วย descriptor-based/safe storage API ตาม platform
  6. authorize file object แยกจาก path validation
func pathInside(root, name string) (string, error) {
	if filepath.IsAbs(name) {
		return "", errors.New("absolute path rejected")
	}
	full := filepath.Clean(filepath.Join(root, name))
	rel, err := filepath.Rel(root, full)
	if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
		return "", errors.New("path escapes root")
	}
	return full, nil
}

ตัวอย่างนี้อธิบาย lexical boundary แต่ยังไม่แก้ symlink race ใช้ object storage หรือ safe filesystem primitives ที่ตรง threat model ใน production

Template Injection และ Output Context

แยก 2 ปัญหา:

  • Server-Side Template Injection — attacker ควบคุม template/expression ที่ engine execute
  • XSS — data ถูก render ใน browser context แล้วกลายเป็น active content

เก็บ template เป็น trusted code ไม่ให้ user สร้าง expression และส่ง data ผ่าน typed model ส่วน output encoding ต้องตรง context:

ContextDefense
HTML textHTML entity encoding/framework auto-escape
HTML attributequoted attribute + attribute encoding
URLvalidate scheme/host + component encoding
JavaScriptหลีกเลี่ยง inline data/code; serialize safely
CSSหลีกเลี่ยง untrusted dynamic CSS; context-specific handling

function sanitize() เดียวใช้ทุก context ไม่ได้ และ encode เร็วเกินไปอาจถูก decode แล้วใช้ผิด context ให้ encode ที่ output sink

Unsafe Deserialization

serialization format ที่สร้าง object/type อัตโนมัติอาจ invoke constructor/gadget หรือเปลี่ยน type จนเกิด side effect แนวทางลด risk:

  • ใช้ data-only format + explicit schema
  • allowlist type/field และ reject unknown field ตาม compatibility policy
  • ห้ามทำ side effect ระหว่าง decode
  • ตรวจ size/depth/count ก่อน allocate
  • authenticate/integrity-protect message เมื่อ source trust ต้องการ
  • version schema และ migration ชัด
  • isolate legacy parser ที่เลิกใช้ไม่ได้

แม้ JSON ปลอดจาก native object gadget บางชนิด แต่ยังทำ memory/CPU exhaustion, type confusion และ business mass assignment ได้

Resource Exhaustion ก็เป็น Input Security

valid input อาจแพงเกิน:

  • JSON nesting ลึกหรือ array ใหญ่
  • regex catastrophic backtracking
  • archive decompression bomb
  • image dimensions/page count สูง
  • GraphQL query breadth/depth/cost
  • filter ที่ทำ database full scan

จำกัด request bytes, parsed elements, depth, time, memory, concurrency และ downstream response พร้อม reject ก่อนงานแพงเมื่อทำได้

Error และ Logging

public error ควรบอกสิ่งที่ client แก้ได้โดยไม่เปิด query, stack, path, key หรือ internal topology ภายในใช้ correlation ID และ structured reason

อย่า log raw malicious payload โดยอัตโนมัติ เพราะ attacker สร้าง log injection, secret/PII retention หรือโจมตี viewer/parser ต่อได้ Normalize/control characters และเก็บ sample ใน restricted evidence path เมื่อจำเป็น

Testing จาก Source ถึง Sink

inventory source → transformations → validators → sink แล้วทดสอบ:

  • alternate encoding/case/Unicode
  • missing/null/wrong type/unknown field
  • min/max/overflow/negative integer
  • duplicate JSON key ตาม parser behavior
  • dynamic query identifier/operator
  • path separator/absolute/symlink
  • concurrent state change
  • oversized/deep/compressed input
  • third-party response ที่ malformed หรือ redirect

fuzzing เหมาะกับ parser/canonicalizer/state machine เมื่อมี oracle เช่น “ไม่ crash, ไม่เกิน resource, ไม่หลุด allowed root, ledger invariant ยังจริง”

Review Checklist

  • input ทุก trust boundary รวม third-party/queue/admin ถูก inventory
  • parse, syntactic, semantic, authorization และ invariant แยกกัน
  • canonical representation ถูกใช้ทั้ง validation และ side effect
  • SQL/NoSQL ใช้ parameterized/server-built query และ allowlist identifier
  • OS operation หลีกเลี่ยง shell พร้อม option/resource/isolation controls
  • file ใช้ opaque ID และ safe root/symlink-aware handling
  • output encoding ตรง HTML/attribute/URL/JS/CSS context
  • deserializer ใช้ explicit schema/type และไม่มี side effect
  • input มี size/depth/time/memory/concurrency limits
  • error/log ไม่เปิด secret, query, path หรือ raw attacker-controlled payload

สรุป

Input security ไม่ใช่ regex 1 ชุด Validation จำกัดรูปแบบ/ความหมาย, parameterization แยก code จาก data, authorization จำกัดสิทธิ์, contextual encoding ป้องกัน output sink และ resource limits ทำให้ input ที่ valid ไม่เปลี่ยนเป็น denial of service

Further Reading