~/VibeHandbook
PDF ฟรี

บท 18 · 02

Injection: เมื่อข้อมูลนำเข้ากลายเป็นโค้ด

Injection เป็นบั๊กร้ายแรงที่เก่าแก่ที่สุดและยังคงพบบ่อยที่สุด มันเกิดขึ้นเมื่อข้อมูลจากผู้ใช้ถูกปฏิบัติเหมือนเป็น คำสั่ง แทนที่จะเป็น ข้อมูล มีสองรูปแบบที่สำคัญสำหรับแอปส่วนใหญ่

injection — SQL (Structured Query Language) คือภาษาที่แอปใช้ขอข้อมูลจากฐานข้อมูล — เกิดขึ้นเมื่อข้อมูลนำเข้าจากผู้ใช้แอบแทรกเข้าไปในคิวรีฐานข้อมูล รูปแบบคลาสสิกที่มีช่องโหว่:

// VULNERABLE: the user's input is glued straight into the query
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// If someone passes  name = '; DROP TABLE users; --
// your query becomes a command to delete the table.

วิธีแก้คือ parameterized query (หรือเรียกอีกอย่างว่า prepared statement) คุณส่งคิวรีและข้อมูลแยกกัน เพื่อให้ฐานข้อมูลไม่มีทางสับสนระหว่างทั้งสองอย่าง:

// SAFE: the value is passed as a parameter, never as code
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query("SELECT * FROM users WHERE name = ?", [name]);
});

กฎคือ: อย่าสร้างคิวรีด้วยการต่อสตริงเด็ดขาด ถ้าคุณเห็นแบ็กติกหรือ + กำลังประกอบ SQL รอบตัวแปรในดิฟฟ์ ให้หยุดและขอเวอร์ชัน parameterized

ทั้งสองเส้นทางนำข้อมูลนำเข้าของผู้ใช้เดียวกันเข้าสู่ฐานข้อมูล แต่มีเพียงเส้นทางเดียวเท่านั้นที่ป้องกันไม่ให้ฐานข้อมูลเข้าใจผิดว่าข้อมูลนั้นเป็นคำสั่ง:

  user input:  '; DROP TABLE users; --
        │
        ├─────────────────────────┬───────────────────────────┐
        ▼                         ▼                            
  ✗ CONCATENATED            ✓ PARAMETERIZED                    
  "...WHERE name=             "...WHERE name = ?", [input]      
     '" + input + "'"          (query and data sent separately)
        │                         │
        ▼                         ▼
  ┌───────────────┐         ┌───────────────┐
  │   DATABASE    │         │   DATABASE    │
  │ reads input   │         │ treats input  │
  │ AS COMMANDS   │         │ as DATA only  │
  │  → table gone │         │  → safe lookup │
  └───────────────┘         └───────────────┘
       BREACH                    SAFE

XSS (cross-site scripting) ก็เป็นแนวคิดเดียวกันแต่เกิดในเบราว์เซอร์ ถ้าคุณนำข้อมูลนำเข้าจากผู้ใช้ไปวางลงในหน้าเว็บเป็น HTML ดิบ (HyperText Markup Language คือโค้ดที่บอกเบราว์เซอร์ว่าจะแสดงผลอะไร) ผู้โจมตีสามารถแทรกแท็ก <script> ที่ทำงานในเบราว์เซอร์ของผู้ใช้คนอื่นได้ — เช่น ขโมยเซสชันของพวกเขา วิธีแก้คือการ escape: แสดงเนื้อหาของผู้ใช้เป็น ข้อความ ไม่ใช่ HTML เฟรมเวิร์กสมัยใหม่อย่าง React จะ escape ให้โดยค่าเริ่มต้นซึ่งดีมาก จนกว่า AI จะหยิบทางลัดอย่าง dangerouslySetInnerHTML หรือ innerHTML มาใช้เพื่อ "ทำให้มันทำงานได้" สิ่งเหล่านั้นจะข้ามการป้องกันไป ให้ตั้งข้อสงสัยกับการเรียกใช้แบบนี้ทุกครั้งที่พบในดิฟฟ์

หลักการรวมเบื้องหลังบั๊ก injection ทุกแบบคือ: แยกข้อมูลกับโค้ดออกจากกัน เมื่อใดที่ข้อมูลนำเข้าของผู้ใช้ข้ามเข้าไปในคิวรี คำสั่ง เทมเพลต หรือ HTML จะต้องมีการ escape หรือ parameterize มันเสมอ

อยากได้แบบออฟไลน์?

ดาวน์โหลดหนังสือทั้งเล่มเป็น PDF หรือ EPUB — ฟรี