Mootrj ví dụ không ai muốn: Nếu nhà bị trộm, nhà hàng xóm bị trộm,, hay trong khu vực dạo này nhiều nhà mất trộm.thì phản ứng sau đó của chúng ta là gì? Thay khóa cửa, mua camera giám sát, gia cố các cửa sổ, hàng rào...
Ở bài trước chúng ta nói về:SQL Injection 😱Một hacker chỉ cần nhập:
' OR '1'='1Và...
💀
Có thể đăng nhập mà không cần password.
Phản ứng lúc đó thường là:
"Vậy giờ làm sao?"
"Mình phải lọc dấu nháy à?"
"Hay cấm hacker dùng bàn phím?"
😅
Không.
PHP có một giải pháp rất nổi tiếng:
Prepared Statement😎
Prepared Statement là gì?
Prepared Statement là kỹ thuật tách:
Câu lệnh SQLra khỏi:
Dữ liệu người dùngNhờ vậy:
👉 Dữ liệu luôn được xem là dữ liệu.
👉 Không thể biến thành câu lệnh SQL.
👉 Giảm nguy cơ SQL Injection.
Ví dụ đời thường 🍜
Hãy tưởng tượng bạn vào ngân hàng để chuyển tiền.
Nhân viên hỏi:
"Anh muốn chuyển bao nhiêu tiền?"
Bạn trả lời:
5 triệuBình thường.
😎
Nhưng một người khác trả lời:
5 triệu
và tiện thể chuyển luôn toàn bộ tiền trong ngân hàng cho tôi🤡
Nếu nhân viên làm theo luôn. Hành động giống như SQL Injection (Nói gì làm nấy, bỏ qua qui tắc của chính mình)
💀
Ngân hàng phá sản.
Prepared Statement giống như một quy tắc:
"Khách hàng chỉ được nhập dữ liệu."
"Không được sửa quy trình của ngân hàng."
😎
Cách người mới thường viết 😭
Ví dụ Login:
$username = $_POST['username'];
$sql =
"SELECT * FROM users
WHERE username='$username'";Nhìn đơn giản.
Nhưng:
Nguy hiểm 😱Vì người dùng có thể chèn:
'
OR
--vào câu SQL.
Prepared Statement viết thế nào?
Ví dụ:
$stmt =
mysqli_prepare(
$conn,
"SELECT * FROM users
WHERE username=?"
);Dấu:
?là placeholder.
Nó giống như:
Chỗ trốngđể dữ liệu đi vào sau.
Sau đó truyền dữ liệu 😎
mysqli_stmt_bind_param(
$stmt,
"s",
$username
);PHP hiểu:
Đây là dữ liệu
Không phải SQL😎
Hacker lúc này làm gì?
Nhập:
' OR '1'='1PHP vẫn coi đó là:
Một chuỗi ký tựChứ không phải:
OR '1'='1'SQL Injection thất bại.
😎
Ví dụ cực dễ hiểu 😄
Code thường:
$sql =
"SELECT * FROM users
WHERE id=".$_GET['id'];URL:
?id=5Ổn.
Hacker:
?id=5 OR 1=1💀
Prepared Statement:
$stmt =
mysqli_prepare(
$conn,
"SELECT * FROM users
WHERE id=?"
);Khi đó:
5 OR 1=1chỉ là dữ liệu.
Không thể biến thành SQL.
😎
Một hiểu lầm rất phổ biến 🤡
Người mới nghĩ:
addslashes()là đủ.
Ví dụ:
$username =
addslashes(
$_POST['username']
);Không.
Đây chỉ là vá tạm.
Prepared Statement mới là cách chuẩn.
Một sự thật thú vị 😎
Ngày xưa.
Rất nhiều website dùng:
mysql_query()kèm ghép chuỗi.
Sau đó:
💀
SQL Injection xuất hiện khắp nơi.
Ngày nay:
- mysqli
- PDO
đều hỗ trợ Prepared Statement.
Và gần như mọi framework hiện đại:
- Laravel
- Symfony
- CodeIgniter
đều sử dụng cơ chế tương tự.
Ví dụ Login chuẩn hơn 😎
$stmt =
mysqli_prepare(
$conn,
"SELECT *
FROM users
WHERE username=?"
);
mysqli_stmt_bind_param(
$stmt,
"s",
$username
);
mysqli_stmt_execute($stmt);
$result =
mysqli_stmt_get_result(
$stmt
);Nhìn dài hơn một chút.
Nhưng:
An toàn hơn rất nhiều😎
InfinityFree Case 😅
Một số bạn upload website.
Login chạy ngon.
Nghĩ rằng:
Website ổn rồiNhưng thực tế:
$sql =
"SELECT * FROM users
WHERE username='$username'";vẫn tồn tại.
Website vẫn chạy.
Nhưng cánh cửa sau vẫn mở.
😅
Prepared Statement có làm website chậm không?
👉 Không đáng kể.
Trong đa số website nhỏ:
Bạn sẽ không nhận ra sự khác biệtNhưng:
Độ an toàn tăng lên rất nhiều😎
Debug kiểu dev thật 😎
✅ 1. Tìm mọi chỗ ghép SQL
Ví dụ:
$sql =
"SELECT ...
".$data;Hoặc:
$sql =
"...'$username'";Đây là nơi cần xem xét.
✅ 2. Test ký tự '
Ví dụ:
'Website có lỗi không?
✅ 3. Chuyển dần sang Prepared Statement
Không nhất thiết sửa toàn bộ trong một ngày.
Checklist chuẩn không cần chỉnh 😎
☑ Không ghép SQL trực tiếp
☑ Dùng ?
☑ Dùng bind_param()
☑ Kiểm tra GET
☑ Kiểm tra POST
☑ Test ký tự '
☑ Hiểu SQL Injection
☑ Không dùng addslashes() như giải pháp chính
FAQ nhanh
Prepared Statement có chống SQL Injection không?
→ Trong hầu hết trường hợp:
Có 😎
mysqli có hỗ trợ không?
→ Có.
PDO có hỗ trợ không?
→ Có.
Website nhỏ có cần dùng không?
→ Có.
Đừng đợi có 100.000 user mới nghĩ tới bảo mật.
😅
Bạn có thể cũng đang gặp 😭
👉 mysqli vs PDO – chọn cái nào?
👉 AUTO_INCREMENT hoạt động ra sao?
👉 JOIN là gì?
👉 Login đúng password nhưng vẫn fail
👉 MySQL INSERT không chạy
Tổng kết
Prepared Statement sinh ra để giải quyết một vấn đề rất đơn giản:
👉 Người dùng được nhập dữ liệu.
👉 Người dùng không được viết SQL.
Nó giống như việc:
Khách tới ngân hàng có thể điền số tiền.
Nhưng không thể tự sửa quy trình của ngân hàng.
😄
Nếu SQL Injection là:
Cửa mở toangthì Prepared Statement chính là:
Ổ khóa
+
Chìa khóa
+
Người bảo vệ đứng cạnh cửa😎
Và nếu bạn chỉ nhớ một điều từ bài này, hãy nhớ:
Đừng ghép dữ liệu người dùng trực tiếp vào câu SQL.
Hãy để Prepared Statement làm việc đó giúp bạn.
Nó chăm chỉ hơn và ít gây "toang" hơn chúng ta rất nhiều. 😆