Injection: khi đầu vào biến thành mã lệnh
Injection (chèn mã độc) là lỗi nghiêm trọng lâu đời nhất và vẫn phổ biến nhất. Nó xảy ra khi dữ liệu từ người dùng bị xử lý như lệnh thay vì dữ liệu. Có hai dạng quan trọng với hầu hết ứng dụng.
injection — SQL (Structured Query Language, ngôn ngữ truy vấn có cấu trúc) là ngôn ngữ mà ứng dụng dùng để hỏi cơ sở dữ liệu () lấy dữ liệu — xảy ra khi đầu vào của người dùng lọt vào một câu truy vấn cơ sở dữ liệu. Khuôn mẫu dễ bị tấn công kinh điển:
// 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.
Cách sửa là dùng truy vấn tham số hóa (parameterized query, còn gọi là prepared statement). Bạn gửi câu truy vấn và dữ liệu riêng biệt, để cơ sở dữ liệu không bao giờ nhầm lẫn cái này với cái kia:
// 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]);
});
Quy tắc: đừng bao giờ dựng một câu truy vấn bằng cách ghép chuỗi (string concatenation). Nếu bạn thấy dấu backtick hoặc + ghép SQL quanh một biến trong một diff (bản thay đổi), hãy dừng lại và yêu cầu phiên bản tham số hóa.
Hai con đường đều đưa cùng một đầu vào của người dùng vào cơ sở dữ liệu, nhưng chỉ một trong hai giữ cho cơ sở dữ liệu không nhầm đầu vào đó là lệnh:
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, tấn công chèn mã qua trang web) cũng là ý tưởng tương tự nhưng ở trong trình duyệt. Nếu bạn lấy đầu vào của người dùng và thả nó vào một trang dưới dạng HTML (HyperText Markup Language, mã cho trình duyệt biết cần hiển thị gì) thô, kẻ tấn công có thể chèn một thẻ <script> chạy trong trình duyệt của những người dùng khác của bạn — ví dụ như để đánh cắp phiên đăng nhập () của họ. Cách sửa là "escaping" (thoát ký tự): hiển thị nội dung người dùng như văn bản thuần, không phải HTML. Các hiện đại như React tự động escape theo mặc định, điều này rất tốt, cho đến khi AI dùng một "lối thoát" (escape hatch) như dangerouslySetInnerHTML hay innerHTML để "làm cho nó chạy". Những thứ đó bỏ qua lớp bảo vệ. Hãy coi bất kỳ lệnh gọi nào như vậy trong một diff là thứ cần đặt câu hỏi.
Nguyên tắc thống nhất đằng sau mọi lỗi injection: giữ dữ liệu và mã lệnh tách biệt. Bất cứ khi nào đầu vào của người dùng đi vào một câu truy vấn, một lệnh, một template, hay HTML, phải có gì đó escape hoặc tham số hóa nó.