- Bước 1: Giới hạn quyền truy cập trang Security Logs
- Bước 2: Kiểm tra bảng log tồn tại trước khi query
- Bước 3: Thu thập và chuẩn hoá input từ URL
- Bước 4: Xây dựng WHERE clause theo từng điều kiện
- Bước 5: Lọc status code theo số hoặc theo nhóm
- Bước 6: Đếm tổng số kết quả để phân trang
- Bước 7: Query dữ liệu theo trang hiện tại
- Bước 8: Hiển thị thời gian theo timezone site
- Bước 9: Thêm chức năng export CSV
- Kết quả cuối cùng
Sau bài này, Init Sentinel hoàn thiện vòng đời: ghi log → quan sát nhanh → điều tra chi tiết.
Bước 1: Giới hạn quyền truy cập trang Security Logs
Trang log bảo mật tuyệt đối không dành cho user thường. Ngay khi vào page callback, cần chặn toàn bộ user không đủ quyền.
if ( ! current_user_can('manage_options') ) {
wp_die( esc_html__('You do not have permission to view this page.', 'init-html') );
}
Điều này đảm bảo chỉ Admin (hoặc role cao tương đương) mới có thể truy cập dữ liệu nhạy cảm.
Bước 2: Kiểm tra bảng log tồn tại trước khi query
Không giả định rằng bảng log luôn tồn tại. Trong lần kích hoạt đầu tiên hoặc khi module bị tắt, bảng có thể chưa được tạo.
$has_table = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = %s",
$table
) );
if ( ! $has_table ) {
echo '<div class="wrap"><h1>Security Logs</h1><p>Security log table not found.</p></div>';
return;
}
Cách làm này giúp wp-admin không bao giờ bị lỗi trắng chỉ vì thiếu bảng.
Bước 3: Thu thập và chuẩn hoá input từ URL
Trang log hỗ trợ nhiều bộ lọc phục vụ điều tra thực tế, mỗi input đều được ép kiểu và làm sạch ngay từ đầu.
$paged = max(1, absint($_GET['paged'] ?? 1));
$per_page = min( max(20, absint($_GET['per_page'] ?? 50)), 200 );
$q = trim((string)($_GET['s'] ?? ''));
$code = trim((string)($_GET['code'] ?? ''));
$endpoint = trim((string)($_GET['endpoint'] ?? ''));
$action = trim((string)($_GET['action_key'] ?? ''));
$user_id = trim((string)($_GET['user'] ?? ''));
$ip = trim((string)($_GET['ip'] ?? ''));
$date_from = trim((string)($_GET['from'] ?? ''));
$date_to = trim((string)($_GET['to'] ?? ''));
Mỗi biến này tương ứng trực tiếp với một tiêu chí lọc trong bảng log.
Bước 4: Xây dựng WHERE clause theo từng điều kiện
Thay vì viết SQL cứng, Init Sentinel xây dựng mảng điều kiện và parameter theo input thực tế.
$where = [];
$params = [];
if ($endpoint !== '') {
$where[] = 'endpoint LIKE %s';
$params[] = $endpoint . '%';
}
if ($action !== '') {
$where[] = 'action LIKE %s';
$params[] = '%' . $wpdb->esc_like($action) . '%';
}
if ($ip !== '') {
$where[] = 'ip_address LIKE %s';
$params[] = '%' . $wpdb->esc_like($ip) . '%';
}
Cách này giúp query an toàn, dễ mở rộng và không bị rối khi thêm filter mới.
Bước 5: Lọc status code theo số hoặc theo nhóm
Status code có thể lọc chính xác hoặc theo nhóm như 4xx, 5xx để phục vụ điều tra nhanh.
if (preg_match('/^\d{3}$/', $code)) {
$where[] = 'status_code = %d';
$params[] = (int)$code;
} elseif (preg_match('/^[245]xx$/', $code)) {
$prefix = (int)$code[0];
$where[] = 'status_code BETWEEN %d AND %d';
$params[] = $prefix * 100;
$params[] = $prefix * 100 + 99;
}
Chỉ riêng đoạn này đã giúp tiết kiệm rất nhiều thời gian khi soi log 403.
Bước 6: Đếm tổng số kết quả để phân trang
Trước khi query dữ liệu, cần biết tổng số dòng để tính số trang.
$total = (int) $wpdb->get_var(
$wpdb->prepare("SELECT COUNT(*) FROM {$table} {$where_sql}", ...$params)
);
Query đếm tách riêng giúp logic rõ ràng và dễ debug.
Bước 7: Query dữ liệu theo trang hiện tại
Dữ liệu log được lấy với LIMIT và OFFSET, đảm bảo mỗi request luôn nhẹ.
$sql = "SELECT id,created_at,user_id,ip_address,endpoint,action,status_code,user_agent
FROM {$table}
{$where_sql}
ORDER BY {$orderby} {$order}
LIMIT %d OFFSET %d";
$rows = $wpdb->get_results(
$wpdb->prepare($sql, ...array_merge($params, [$per_page, $offset]))
);
orderby luôn bị giới hạn whitelist để tránh SQL injection.
Bước 8: Hiển thị thời gian theo timezone site
Log được lưu bằng UTC, nhưng hiển thị theo timezone WordPress để admin dễ đọc.
$time_local = get_date_from_gmt(
gmdate('Y-m-d H:i:s', strtotime($r->created_at)),
get_option('date_format').' '.get_option('time_format')
);
Dữ liệu gốc không bao giờ bị biến đổi.
Bước 9: Thêm chức năng export CSV
Trang log hỗ trợ xuất CSV phục vụ điều tra offline hoặc chia sẻ cho đội kỹ thuật.
if ( isset($_GET['export']) && $_GET['export'] === 'csv' ) {
check_admin_referer('init_html_sentinel_export');
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=security-logs.csv');
}
Export bị giới hạn số dòng và bắt buộc nonce để tránh lạm dụng.
Kết quả cuối cùng
Trang Security Logs này là trung tâm điều tra của Init Sentinel. Mọi hành vi bị chặn đều có thể được lọc, soi và truy vết tại đây.
Đây là mảnh ghép cuối cùng hoàn thiện Init Sentinel như một hệ thống theo dõi truy cập trái phép đúng nghĩa trong WordPress.
Lý tưởng nhất là trang này luôn trống. Nếu không, tức là có kẻ đang lục lọi hệ thống.
Bình luận