Thứ Sáu, 19 tháng 6, 2026

Vì sao nên dùng Prepared Statement? (Khi bạn cuối cùng cũng học được cách khóa cửa thay vì ngồi cầu nguyện hacker đừng ghé thăm)

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'='1

Và...

💀

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 SQL

ra khỏi:

Dữ liệu người dùng

Nhờ 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ệu

Bì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'='1

PHP 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=1

chỉ 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ồi

Như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ệt

Như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 😭

👉 SQL Injection là gì?

👉 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ở toang

thì 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. 😆