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 มันเสมอ