🛡️
← Secure by Design

SECURE BY DESIGN DAY 09

เริ่มทำควิซ ⚡
Part 3: Applying the Fundamentals | Chapter 9

Handling Failures Securely

Fail-Secure & Zero Leakage ⚡
📌 TITLE ANCHOR QUESTION ทำไมบทนี้ถึงชื่อนี้? และการรับมือ Exception เกี่ยวข้องกับ Security อย่างไร?

คำตอบและหมุดเชื่อมโยง: ระบบซอฟต์แวร์เปรียบเสมือนป้อมปราการที่แน่นหนาในยามปกติ แต่เมื่อเกิดความผิดพลาด (Failure/Exception) ระบบมักจะเข้าสู่ "สภาวะปวนแปร (Unstable State)"

ผู้เขียนตั้งชื่อบทนี้ว่า "Handling failures securely" เพื่อเตือนเราว่า แฮกเกอร์ส่วนใหญ่มักป้อนข้อมูลขยะเข้ามาเพื่อ "จงใจทำให้ระบบพัง" แล้วฉวยโอกาสโจมตี 2 ทางหลัก: 1) ขโมยข้อมูลความลับ (Confidentiality Leak) จาก Stack Trace ที่หลุดออกสู่หน้าจอ API และ 2) ทำลายระบบ (Availability Attack) ให้เกิด Cascading Failures ล่มตามกันทั้งเซิร์ฟเวอร์! การจัดการความผิดพลาดอย่างปลอดภัย จึงเป็นการรับประกันว่าระบบจะ Fail-Secure เสมอ, รั่วไหลข้อมูลความลับเป็นศูนย์, และใช้สถาปัตยกรรม Circuit Breaker & Bulkhead สยบการล่มลอยแพครับ!

Part 1 & 2 ที่ผ่านมา (Recap)

Day 1-7: วางรากฐาน DDD, Immutability, Domain Primitives, State Integrity และ Entity Relay

ตำแหน่งปัจจุบัน (Where We Are)

Day 9 จาก 14 วัน — เข้าสู่ใจกลาง Part 3 สวมเกราะให้ Exception และระบบประมวลผลยามเกิดวิกฤต

เป้าหมายประจำวัน (Core Mission)

เรียนรู้ Zero Sensitive Leakage, หลักการ Fail-Secure และ Resilience Patterns (Circuit Breaker & Bulkhead)

02

เรื่องราวและมโนทัศน์เชิงลึก (Real Scenarios, Architecture & Diagrams)

🔓 SCENARIO 2.1 • โศกนาฏกรรม STACK TRACE รั่วไหล และคดี SQL ERROR LEAKAGE Information Disclosure

📖 เรื่องราวจริง: อัญประกาศเดี่ยวตัวเดียวทำพังทั้งระบบ

ระบบสืบค้นข้อมูลผู้ป่วยของโรงพยาบาลแห่งหนึ่งเปิดให้คนไข้ค้นหาประวัติผ่านหน้าเว็บ ผู้ใช้คนหนึ่งทดลองพิมพ์อักขระอัญประกาศเดี่ยว (') ลงในช่องค้นหา ระบบฝั่ง Server เกิด PSQLException และตอบกลับหน้าเว็บด้วย HTTP 500 พร้อม Stack Trace ยาว 80 บรรทัด บนหน้าจอผู้ใช้!

💥 ข้อมูลความลับที่หลุดออกไปทันที:
• ไวยากรณ์ DB: ERROR: syntax error at or near "'"
• ชื่อตารางในฐานข้อมูล: patient_records
• ชื่อคอลัมน์สำคัญ: ssn_number, medical_history_notes
• โครงสร้างไดเรกทอรี Server: /var/www/hospital/dao/PatientDAO.java:142

ผลลัพธ์: แฮกเกอร์ไม่ต้องเดาโครงสร้างฐานข้อมูลเลย นำชื่อตารางและคอลัมน์ที่ได้ไปยิง SQL Injection ดูดประวัติคนไข้ทั้งหมดออกไปได้อย่างแม่นยำ!

🛡️ สถาปัตยกรรม Error Sanitization & Correlation ID

กฎเหล็ก: "Internal Exceptions stay inside, Public APIs get sanitised Domain Errors"

เมื่อเกิด Technical Exception ขึ้น ฝั่ง Server ต้องบันทึกรายละเอียดฉบับเต็มลงใน Log ที่ปลอดภัย แล้วคืนรหัสสุ่ม Correlation ID (UUID) พร้อมข้อความทั่วไปให้ Client แทน

{ "status": "ERROR", "message": "An unexpected error occurred", "referenceId": "a8f1b402-99c1-4b12" }
🎯 ทีมงานสามารถนำรหัส a8f1b402... ไปค้นหา Stack Trace ฉบับเต็มใน Server Log ได้ทันที โดยที่แฮกเกอร์ภายนอกมองไม่เห็นโครงสร้างภายในเลยแม้แต่ตัวเดียว!
🚪 SCENARIO 2.2 • สวิตช์ประตูกลไฟไหม้: หลักการ FAIL-SECURE VS. FAIL-OPEN Default Deny Policy

🚒 อุปมาอุปไมยในโลกจริง (The Physical Metaphor)

🚪 ประตูหนีไฟอาคาร (Fail-Open / Fail-Safe)

หากเกิดไฟไหม้และไฟดับ ระบบล็อกประตูไฟฟ้าต้อง "ปลดล็อกเปิดอ้าออกอัตโนมัติ" เพื่อให้คนหนีรอดชีวิตได้ เพราะความปลอดภัยของชีวิตมนุษย์สำคัญที่สุด (Safety Priority)

🔐 ประตูห้องเซิร์ฟเวอร์ความลับ (Fail-Secure / Fail-Closed)

หากระบบสแกนลายนิ้วมือเกิดไฟฟ้าลัดวงจร ประตูห้องนิรภัยต้อง "ล็อกแน่นหนา ปฏิเสธทุกคน" เพื่อรักษาความลับของข้อมูล ไม่ให้ใครฉวยโอกาสผลักประตูเข้าไปได้ (Security Priority)

💻 ถอดรหัสสู่โค้ดระบบตรวจสอบสิทธิ์ (Authorization)

ในระบบซอฟต์แวร์ การจัดการสิทธิ์ต้องเป็น Fail-Secure (Deny by Default) เสมอ:

// ห้ามเขียน Fail-Open เด็ดขาด:
try { checkUserPermission(); } catch(Exception e) { /* ปล่อยผ่าน */ }
// โค้ดที่ถูกต้องแบบ Fail-Secure:
try {
  authService.verify(user, resource);
} catch (Exception e) {
  throw new AccessDeniedException("Access Denied", e);
}
⚡ หากการเชื่อมต่อกับ LDAP หรือ Auth Server ขัดข้อง ระบบต้องตีความว่า "ไม่อนุญาตให้เข้าถึง" ทันที ไม่ใช่ปล่อยผ่านเงียบๆ
💳 SCENARIO 2.3 • ระบบชำระเงินล่มลอยแพ (CASCADING FAILURE) และสถาปัตยกรรม RESILIENCE High Availability

🎟️ เรื่องราวจริง: เว็บขายตั๋วคอนเสิร์ตล่มทั้งระบบ

เมื่อเปิดขายตั๋วพร้อมกัน บริการภายนอกของธนาคาร (Payment Gateway) เกิดปัญหาตอบสนองช้าลง จากเดิม 200ms กลายเป็น 30 วินาทีต่อคำสั่ง

💥 Cascading Failure (ล่มสะท้อน): เมื่อคน 1,000 คนกดจ่ายเงินพร้อมกัน ทุก Thread ของเซิร์ฟเวอร์ถูกดึงไปรอนาน 30 วินาที เกิดภาวะ Thread Starvation ทรัพยากรหมดเกลี้ยง ส่งผลให้ผู้ใช้คนอื่นที่แค่ต้องการ "ดูตารางงาน" หรือ "เปิดหน้าแรก" ไม่สามารถเข้าใช้งานได้เลย เซิร์ฟเวอร์ล่มดับไปทั้งเว็บ!

🛡️ 2 อาวุธกู้ชีพ: Circuit Breaker & Bulkhead

⚡ 1. Circuit Breaker (สวิตช์ตัดไฟอัตโนมัติ)

สังเกตอัตราความล้มเหลว เมื่อความช้าหรือ Error เกินเกณฑ์ สวิตช์จะเปลี่ยนเป็น "Open (ตัดวงจร)" ทันที ตัดสายไม่ส่ง Request ไปรอ Payment อีกต่อไป แต่จะ Fail-Fast คืนข้อความให้ผู้ใช้ทันที เซฟ Thread ของระบบหลักไว้ 100%!

🚢 2. Bulkhead Pattern (ผนังกั้นน้ำเรือดำน้ำ)

แบ่งโควตา Thread Pool ของแต่ละโมดูลออกจากกันอย่างเด็ดขาด เช่น ระบบจ่ายเงินได้ 20 Threads, ระบบค้นหาได้ 50 Threads หากฝั่งจ่ายเงินตัน ฝั่งค้นหายังคงแล่นได้ตามปกติ เรือไม่จมลงทั้งลำ!

STATE TRANSITION: CIRCUIT BREAKER PATTERN
1
CLOSED (วงจรปิด/ปกติ)

คำสั่งไหลผ่านปกติ หากมีข้อผิดพลาดสะสมเกินเกณฑ์ (Threshold) จะดีดไปสถานะ OPEN

2
OPEN (วงจรเปิด/ตัดไฟ)

ไม่ยิงหา External Service อีกต่อไป แต่จะ Fail-Fast ทันที เซฟ Threads ไว้ รอจนครบเวลา Cool-down

3
HALF-OPEN (ลองปล่อยชิมลาง)

ปล่อยคำสั่งตัวอย่างทดลองรัน 2-3 รายการ หากสำเร็จทั้งหมดจะกลับไป CLOSED หากล้มเหลวจะดีดกลับไป OPEN

03

แบบฝึกหัดปฏิบัติตามสถานการณ์จริง (Hands-on Refactoring)

Before: Leaky Logging & Raw Stack Trace เสี่ยงเปิดเผย Schema & รหัสผ่านรั่ว
// โค้ดเดิม: พ่น Stack Trace ให้ Client, พิมพ์ Password ลง Log
public class PaymentController {
    private static final Logger logger = LoggerFactory.getLogger(PaymentController.class);
    private ExternalPaymentGateway gateway = new ExternalPaymentGateway();

    public Response processPayment(String userId, String rawPassword, double amount) {
        try {
            // 💥 ช่องโหว่ 1: พิมพ์ Plaintext Password ลงไฟล์ System Log!
            logger.info("User: " + userId + " pass: " + rawPassword);

            // 💥 ช่องโหว่ 2: เรียก Gateway ตรงๆ ไม่มี Timeout/Circuit Breaker (เสี่ยงค้าง)
            boolean ok = gateway.charge(userId, amount);
            return new Response("SUCCESS", ok);

        } catch (Exception e) {
            // 💥 ช่องโหว่ 3: พ่น Stack Trace และ SQL Syntax ให้ Client เห็นทั้งหมด!
            return new Response("ERROR", e.getMessage() + " Stack: " + Arrays.toString(e.getStackTrace()));
        }
    }
}
⚠️ จุดบกพร่องร้ายแรง: รหัสผ่านรั่วไหลลงไฟล์ Log บนเซิร์ฟเวอร์, หาก Gateway ภายนอกช้าจะดึง Thread ทั้งเว็บจนล่มตามกัน, และส่งชื่อตาราง/คอลัมน์ SQL คืนไปให้แฮกเกอร์ใช้เจาะระบบ
After: Sanitized Response, Masked Log & Circuit Breaker Zero Sensitive Leakage
public class PaymentController {
    private static final Logger logger = LoggerFactory.getLogger(PaymentController.class);
    private final ResilientPaymentService paymentService;

    public Response processPayment(UserId userId, Password password, Money amount) {
        // 1. สร้าง Correlation ID สำหรับสอบทานใน Log ภายใน
        String correlationId = UUID.randomUUID().toString();

        try {
            // 2. รหัสผ่านถูก Mask อัตโนมัติเป็น ***** ใน Domain Primitive ป้องกัน Log รั่ว
            logger.info("Processing payment CorrelationID: {}, User: {}", correlationId, userId.value());

            // 3. เรียกผ่าน Circuit Breaker & Timeout (Fail-Secure & ไม่ล่มสะท้อน)
            PaymentResult result = paymentService.chargeResiliently(userId, amount);
            return new Response("SUCCESS", result.transactionId());

        } catch (CircuitBreakerOpenException e) {
            // ตัดวงจรทันที คืนผลเร็ว เซฟ Thread
            logger.warn("Circuit Open for Payment. CorrelationID: {}", correlationId);
            return new Response("BUSY", "Service temporarily unavailable. Ref: " + correlationId);

        } catch (Exception e) {
            // 4. Sanitized Error: ซ่อน Stack Trace ฉบับจริงไว้ใน Server คืนแค่ Generic Error
            logger.error("Payment Error CorrelationID: " + correlationId, e);
            return new Response("SYSTEM_ERROR", "An unexpected error occurred. Ref: " + correlationId);
        }
    }
}
🛡️ จุดเปลี่ยนสำคัญ: รหัสผ่านไม่รั่วลง Log, ซ่อน Stack Trace จากภายนอก 100% โดยคืนเฉพาะ Correlation ID, และมี Circuit Breaker คอยตัดวงจรเมื่อ Gateway ภายนอกช้า ป้องกันระบบหลักล่มตามกัน!
04

ชุดควิซประเมินความรู้ประจำวัน

ตอบปุ๊บ รู้ผลเฉลยทันที พร้อมคำอธิบายและหลอดพลังงาน Energy Charge

Energy Charge
0 / 10
📌 CLOSING TITLE CONNECTION WRAP-UP สรุปหมุดเชื่อมโยงกลับหาหัวข้อบท

สรุปการเชื่อมโยงกลับสู่ Chapter 9 — Handling failures securely

เรื่องราวของ โศกนาฏกรรม Stack Trace รั่วไหล, หลักการ Fail-Secure ประตูกล, และการสวมสเกล Circuit Breaker & Bulkhead สยบ Cascading Failure ในวันนี้ ล้วนตอกย้ำหัวข้อบท "Handling failures securely" อย่างสมบูรณ์แบบว่า ความปลอดภัยที่แท้จริงต้องครอบคลุมถึงยามที่ระบบพัง การซ่อนรายละเอียดทางเทคนิคจากภายนอก, การยึดหลัก Deny by Default, และการตัดวงจรอัตโนมัติ จะช่วยเปลี่ยนสภาวะที่เปราะบางให้กลายเป็นเกราะป้องกันที่แข็งแกร่งที่สุดครับ!

พร้อมก้าวสู่มุมมองระบบคลาวด์ในบทถัดไปแล้วหรือยัง? Next: Day 10 — Chapter 10: Benefits of cloud thinking (Stateless & Micro-segmentation) ➔