Hàm in_array() trong PHP là một công cụ phổ biến để kiểm tra sự tồn tại của giá trị trong mảng, nhưng việc bỏ qua tham số thứ ba — cờ kiểm tra kiểu — có thể dẫn đến lỗ hổng nghiêm trọng trong logic xác thực, đặc biệt khi xử lý dữ liệu người dùng không đáng tin cậy.
Vấn đề từ cơ chế so sánh yếu
Khi không truyền tham số thứ ba (mặc định là false), in_array() thực hiện so sánh yếu (loose comparison) — nghĩa là PHP sẽ tự động ép kiểu để so sánh. Điều này gây ra hành vi không mong muốn khi giá trị đầu vào chứa cả ký tự số và chữ cái.
Xét ví dụ sau:
<?php
$allowed_ids = range(1, 24);
$user_input = '3shell.php';
// Không có tham số thứ ba → so sánh yếu
if (in_array($user_input, $allowed_ids)) {
echo "Được chấp nhận!";
} else {
echo "Bị từ chối.";
}
?>
Mã trên in ra "Được chấp nhận!" vì PHP chuyển '3shell.php' thành 3 khi so sánh với các số nguyên trong mảng — dẫn đến việc bỏ qua hoàn toàn phần mở rộng độc hại.
Giải pháp đúng: Kích hoạt kiểm tra kiểu mạnh
Luôn thiết lập tham số thứ ba là true để bắt buộc so sánh kiểu dữ liệu:
<?php
$allowed_ids = range(1, 24);
$user_input = '3shell.php';
if (in_array($user_input, $allowed_ids, true)) {
// Chỉ trả về true nếu $user_input là số nguyên chính xác nằm trong dãy
echo "Hợp lệ.";
} else {
echo "Bị từ chối — kiểm tra kiểu mạnh đã chặn.";
}
?>
Tương tự, với đầu vào SQL như '5 OR 1=1', so sánh yếu sẽ chuyển nó thành 5, khiến kiểm tra danh sách trắng thất bại hoàn toàn.
Ứng dụng thực tế: Lỗi xác thực ID dẫn đến SQL injection
Một tình huống điển hình xuất hiện trong hệ thống quản lý người dùng:
<?php
// Giả sử $whitelist được tạo từ COUNT(*) của bảng users → [1,2,3,4,5]
$id = $_GET['id'] ?? '';
if (!in_array($id, $whitelist)) { // ❌ Thiếu tham số thứ ba
die("ID không hợp lệ");
}
// Sau đó dùng trực tiếp trong truy vấn
$query = "SELECT * FROM users WHERE id = $id"; // ⚠️ Nguy cơ injection
?>
Với payload ?id=1' AND updatexml(1,concat(0x7e,(SELECT flag FROM flag)),0) AND '1'='1, hệ thống vẫn vượt qua kiểm tra in_array() do so sánh yếu, đồng thời tránh được bộ lọc từ khóa nếu hàm updatexml() không nằm trong danh sách bị chặn.
Chiến lược phòng thủ
- Luôn sử dụng
in_array($needle, $haystack, true)khi cần đảm bảo tính chính xác kiểu dữ liệu. - Kết hợp xác thực kiểu dữ liệu ở tầng đầu vào:
filter_var($input, FILTER_VALIDATE_INT). - Tránh nối chuỗi trực tiếp vào truy vấn SQL — thay vào đó dùng prepared statements.
- Không phụ thuộc duy nhất vào kiểm tra danh sách trắng cho dữ liệu người dùng; kết hợp kiểm tra đen và chuẩn hóa đầu vào.