Cập nhật 08/2026: Bài này được viết lại từ một case forensic cũ trên WordPress. Domain, đường dẫn, credential-shaped text và URL độc hại đã được che hoặc loại bỏ. Phần “đã quan sát” được tách khỏi phần “giả thuyết”; không đăng payload webshell để tránh biến bài phân tích thành hướng dẫn lạm dụng.
Trên một website bị xâm nhập, trang chủ có thể vẫn chạy bình thường. Người quản trị đăng nhập được, khách hàng vẫn xem sản phẩm, nhưng Google đã nhìn thấy những URL khác: casino, landing page lạ hoặc các trang không ai trong team tạo ra. Đến khi phát hiện một file PHP không rõ nguồn gốc, câu chuyện thường đã đi xa hơn một file lẻ.
Bài này kể lại một case WordPress có chuỗi dấu hiệu gồm webshell, file bị thay, sitemap spam và cloaking. Mục tiêu không phải đoán tên nhóm tấn công, mà là chỉ ra cách đọc artifact, xếp giả thuyết theo bằng chứng và xử lý sao cho không xóa mất evidence ngay từ bước đầu.
Tóm tắt nhanh
- Parasite SEO trên site bị hack thường để lại nhiều lớp: file điều khiển, nội dung spam, sitemap rác và logic hiển thị khác nhau cho bot/người.
- Một file như promo.php cho thấy khả năng điều khiển sau khi xâm nhập; nó chưa tự chứng minh hacker vào bằng FTP, XML-RPC hay một plugin cụ thể.
- Đừng chỉ xóa file lạ. Hãy giữ snapshot/log, cô lập phạm vi, đổi credential, rebuild từ nguồn sạch và kiểm tra cả database, cron, user lẫn máy local.
- wp core verify-checksums giúp kiểm tra core WordPress, nhưng không kiểm tra hết plugin, theme, database hay server.
- Sau cleanup, dùng Search Console Security Issues và URL Inspection. Google lưu ý sample URL chỉ là mẫu, không phải toàn bộ URL bị ảnh hưởng.
Bài này đang phân tích case nào?
Case gốc được mô tả trên nttung.dev ghi nhận một website WordPress + WooCommerce vẫn hoạt động như thường, nhưng trong thư mục web lại xuất hiện file PHP không ai nhận là của mình. Phân tích sau đó mô tả thêm một file trong wp-content, một XML sitemap chứa URL spam và một ảnh làm asset cho trang giả.
Đây là dữ liệu của một case được ghi lại, không phải kết quả mình vừa tái dựng trên staging. Vì vậy, bài dùng ba nhãn:
- Quan sát: file, kích thước, vị trí hoặc hành vi được ghi nhận trong case.
- Suy luận: khả năng về mục đích hoặc chuỗi hành động dựa trên các artifact.
- Chưa kết luận: điều cần access log, auth log, backup hoặc timeline để xác nhận.
Cách chia này nghe có vẻ chậm, nhưng incident response mà đoán quá sớm thường dẫn tới việc xóa nhầm evidence hoặc đổ lỗi cho sai vector.
Dấu hiệu đầu tiên: site vẫn bình thường nhưng search đã khác
Dấu hiệu dễ thấy nhất không phải lúc nào cũng nằm ở trang chủ. Có thể là một URL tiếng Indonesia xuất hiện khi tìm site:example.com, một redirect chỉ xảy ra khi click từ Google, hoặc một trang trả 404 khi bạn mở trực tiếp nhưng lại có nội dung khi Google crawl.
Google mô tả hacked content là nội dung được đặt lên site không có sự cho phép do lỗ hổng bảo mật. Các pattern thường gặp gồm chèn link, chèn trang mới và redirect người dùng tới nơi khác. Google cũng lưu ý rằng hacker có thể phục vụ nội dung khác nhau theo referrer, thiết bị hoặc crawler.
Trong case này, homepage không bị deface. Điều đó không có nghĩa website an toàn; nó chỉ cho thấy mục tiêu là giữ quyền truy cập lâu hơn và dùng uy tín sẵn có của domain cho nội dung khác.
Chuỗi artifact: từ webshell tới SEO spam
Bốn artifact trong bài gốc tạo thành một chuỗi dễ đọc hơn nếu đặt cạnh nhau:
| Artifact | Điều quan sát được trong case | Vai trò có thể có |
|---|---|---|
| promo.php | File PHP nhỏ, có giao diện thao tác file | Webshell hoặc file manager do attacker đặt |
| wp-content/index.php | File mặc định bị thay bằng file lớn hơn nhiều | Doorway/SEO spam và logic cloaking |
| blackpink.xml | XML có nhiều URL không thuộc nội dung site | Sitemap dẫn crawler tới nội dung spam |
| Ảnh asset | File ảnh đi kèm trang giả | Làm trang spam trông giống một website thật |
Bảng này nói về mối liên hệ trong case, không phải công thức mọi cuộc tấn công đều đi qua đúng bốn file. Nếu thấy một file lạ, cần xem timestamp, owner, access log, backup và các thay đổi liên quan trước khi gắn nhãn malware.
File quản lý web có quyền đọc, ghi và upload
Webshell nguy hiểm không phải vì tên file nghe “hacker”. Nó nguy hiểm vì nó biến một request HTTP thành thao tác trên filesystem: đọc file mà process được phép đọc, ghi đè file, upload file mới, tạo thư mục hoặc xóa dấu vết.
Trong case, file được mô tả có các nhóm chức năng đó nhưng không có lớp xác thực, giới hạn đường dẫn, kiểm tra loại file hay bảo vệ CSRF. Nếu request tới được file và process có quyền, người xâm nhập có một bảng điều khiển nhỏ nằm ngay trong webroot.
Mình không đưa endpoint, tham số hay code hoàn chỉnh của webshell vào đây. Với bài public, phần cần giữ là capability và dấu hiệu điều tra:
- file PHP mới xuất hiện ở vị trí không hợp lý;
- thời gian sửa file khớp với thời điểm access bất thường;
- owner/group khác với pattern bình thường của site;
- file có chức năng đọc/ghi/upload nhưng không thuộc plugin/theme nào;
- có request POST tới file không nằm trong route ứng dụng.
Tên promo.php là một ví dụ của masquerading: đặt tên hiền để file trông giống một tính năng cũ. MITRE ATT&CK gọi đây là cách làm artifact có vẻ hợp lệ hoặc vô hại để né quan sát. Xem mô tả T1036 của MITRE. Tên file chỉ là tín hiệu, không phải bằng chứng actor.
File core bị thay và nội dung spam “vô hình”
WordPress có một index.php rất nhỏ trong wp-content để ngăn directory listing. Trong case, file này được mô tả là đã bị thay bằng một file lớn, chứa HTML/CSS/JS và logic phục vụ trang spam.
Điều đáng chú ý không phải con số kích thước, mà là sự thay đổi vai trò: một file bảo vệ thư mục bị biến thành cửa vào cho nội dung không thuộc site. Nếu core hoặc file mặc định bị thay, đừng sửa từng dòng bằng tay rồi coi như xong; hãy so với bản sạch và xác định thời điểm thay đổi.
Sitemap spam đưa crawler tới nơi attacker muốn
Sitemap vốn là bản đồ các URL mà site muốn công cụ tìm kiếm biết tới. Khi một XML lạ chứa hàng trăm URL casino, slot hoặc brand không liên quan xuất hiện dưới domain của bạn, đó là dấu hiệu SEO spam đáng kiểm tra.
Sitemap không tự tạo ra quyền index. Nó chỉ làm các URL rác dễ được phát hiện hơn. Cần kiểm tra đồng thời robots.txt, Search Console, response status, canonical, database và log crawl để biết spam đang nằm ở file, route, database hay tất cả các lớp.
Cloaking hoạt động ra sao và vì sao admin dễ bỏ sót?
Google định nghĩa cloaking là đưa nội dung khác nhau cho công cụ tìm kiếm và người dùng với mục đích lạm dụng. Trong case này, logic được mô tả theo chuỗi:
- đọc User-Agent để đoán request có phải bot hay không;
- đọc Referer để đoán người dùng đến từ kết quả tìm kiếm;
- redirect người thật từ search sang domain khác;
- dùng query parameter để sinh nhiều biến thể URL;
- random title/mô tả để tạo cảm giác nội dung luôn mới;
- trả status khác nhau cho bot và người;
- dùng giao diện quen mắt để trang spam trông hợp pháp hơn.
Đây là một pattern phân tích, không phải danh sách điều kiện bắt buộc. User-Agent có thể bị giả, Referer có thể vắng mặt, và một response 404 từ trình duyệt không chứng minh Google đã thấy gì.
Khi cần kiểm tra an toàn, đừng mở URL nhiễm bằng tài khoản chính hoặc trình duyệt đang đăng nhập nhiều dịch vụ. Dùng URL Inspection trong Search Console, xem raw response từ môi trường kiểm soát và đối chiếu access log. Google cũng cảnh báo rằng người quản trị có thể không tái hiện đúng nội dung nếu hacker đang cloaking theo referrer hoặc thiết bị. Security Issues report là nơi nên xem trước khi tự kết luận.
Vector xâm nhập: xếp giả thuyết theo bằng chứng
Bài gốc nêu ba khả năng: FTP không mã hóa, XML-RPC brute force và plugin lỗi thời. Cả ba đều là hướng điều tra hợp lý, nhưng webshell xuất hiện sau incident không đủ để chọn một nguyên nhân.
Điều case gợi ý
Case có nhắc tới file cấu hình FTP chứa credential dạng plain text và port 21 mở public. Nếu đúng như mô tả, credential có thể đã bị lộ ở máy developer, repository, backup hoặc đường truyền. Từ đây, việc cần làm là rotate credential và kiểm tra log đăng nhập; không nên viết thành kết luận “FTP là vector” khi chưa có timestamp khớp.
Điều cần log để xác nhận
Muốn phân biệt FTP, SSH/SFTP, XML-RPC hay plugin vulnerability, cần ít nhất:
- access log web quanh thời điểm file đầu tiên xuất hiện;
- auth log của FTP/SSH/SFTP hoặc panel hosting;
- lịch sử deploy, Git và backup;
- version WordPress, plugin, theme tại thời điểm đó;
- tài khoản admin mới, cron mới và thay đổi trong database;
- dấu vết trên máy local của người từng giữ credential.
Không có log thì hãy gọi đó là giả thuyết. Bản rewrite bỏ các xác suất trong bài cũ vì không có phép đo đủ để bảo vệ chúng.
XML-RPC cần được nói cho đúng
WordPress có filter xmlrpc_enabled, nhưng tài liệu core ghi rõ filter này chủ yếu kiểm soát các XML-RPC method cần authentication; nó không đồng nghĩa mọi XML-RPC request đã biến mất. Tài liệu WordPress cũng chỉ ra rằng pingback hoặc endpoint khác cần control riêng.
Vì vậy, câu hỏi đúng không phải “có tắt XML-RPC chưa?”, mà là: site có cần XML-RPC không, method nào đang dùng, rate limit nằm ở đâu và access log có thấy pattern thử mật khẩu không?
30 phút đầu nên làm gì?
Mục tiêu đầu tiên là giảm khả năng attacker tiếp tục ghi file, đồng thời giữ lại đủ thông tin để biết chuyện gì đã xảy ra.
1. Ghi nhận trước khi dọn
Ghi lại thời điểm phát hiện, URL triệu chứng, tài khoản đang đăng nhập, process/service đang chạy và các file đã thấy. Chụp màn hình phải che domain, email, IP private, token và credential.
Nếu có quyền server, tạo snapshot hoặc bản sao chỉ đọc trong storage hạn chế quyền. Không upload archive chứa wp-config.php lên dịch vụ phân tích công cộng.
2. Cô lập theo runbook của hạ tầng
Nếu incident còn hoạt động, hạn chế public traffic ở firewall/security group hoặc chuyển site sang maintenance page ngoài WordPress. Giữ một kênh quản trị riêng cho đội xử lý. Đừng vội xóa webroot hoặc đổi hàng loạt quyền file khi chưa có bản sao evidence.
3. Thu hồi credential ở mọi lớp
Đổi password và revoke key/token cho hosting, SSH/SFTP/FTP, database, WordPress admin, CDN, Search Console, CI/CD và dịch vụ bên thứ ba. Tạo lại WordPress Authentication Keys & Salts sau khi đã kiểm tra quyền truy cập. Nếu credential từng nằm trên máy developer, quét máy đó và coi credential cũ là đã lộ.
4. Tìm persistence
Kiểm tra user WordPress, plugin/theme lạ, mu-plugin, cron của user chạy PHP, systemd timer, .htaccess, wp-config.php, file PHP mới và database options/posts. Một backdoor còn nằm trong cron hoặc database sẽ quay lại sau khi bạn xóa file đầu tiên.
Làm sạch và phục hồi theo hướng rebuild
Nếu không có timeline đầy đủ, rebuild từ nguồn sạch thường dễ kiểm soát hơn là “lau” từng đoạn PHP. WordPress cũng khuyến nghị ghi nhận incident, kiểm tra máy local và so sánh file với bản chính thức trong FAQ về site bị hack. Khi server đã được dựng lại, bạn có thể đối chiếu thêm hướng dẫn cài WordPress trên Ubuntu để kiểm tra lại quyền, UFW và HTTPS.
Kiểm tra core bằng checksum
Sau khi có bản sao evidence, chạy trên bản staging hoặc bản đang được cô lập:
sudo -u www-data wp core verify-checksums \
--include-root \
--path=/var/www/example.com
Lệnh này so checksum core với WordPress.org và include-root cảnh báo file/thư mục ngoài core. Nó không chứng minh plugin, theme, database hoặc server đã sạch. Tài liệu WP-CLI cũng ghi rõ phạm vi của lệnh.
Cài lại từ nguồn sạch
- tải lại WordPress core từ nguồn chính thức;
- cài lại plugin/theme từ vendor hoặc repository đáng tin, đúng version cần dùng;
- không copy lại file PHP từ webroot bị xâm nhập;
- chỉ đưa uploads và dữ liệu đã kiểm tra sang staging;
- diff wp-config.php, .htaccess, mu-plugin và các file bootstrap;
- review user/admin, options, post content và cron trước khi mở public.
File upload là một surface riêng. OWASP khuyến nghị allowlist extension, kiểm tra loại file thật, đổi tên, giới hạn size/quyền và chống CSRF; đừng chỉ dựa vào Content-Type do client gửi. File Upload Cheat Sheet của OWASP là checklist tốt để đối chiếu.
Kiểm tra Google sau cleanup
Sau khi staging đã sạch và bản public đã được kiểm tra từ bên ngoài:
- mở Search Console Security Issues;
- xem sample URL và dùng URL Inspection để kiểm tra cách Google thấy trang;
- kiểm tra Manual Actions nếu có spam ranking;
- tìm site:example.com cùng các nhóm từ không liên quan;
- kiểm tra sitemap, robots, canonical, redirect và status code;
- fix toàn site, không chỉ vài sample URL;
- gửi Request Review khi đã có mô tả rõ mình đã sửa gì.
Google ghi rõ sample URL trong Security Issues chỉ là mẫu, có thể không đầy đủ; xử lý một vài URL chưa đủ để kết luận. Tài liệu Security Issues report cũng hướng dẫn kiểm tra, test và gửi review sau cleanup.
Phòng ngừa để không quay lại điểm cũ
Không có một plugin nào thay cho quy trình vận hành. Sau incident, ưu tiên những việc làm giảm quyền và tăng khả năng phát hiện:
- bỏ FTP plaintext; dùng SSH/SFTP với key, MFA hoặc policy phù hợp;
- cập nhật core, plugin, theme và xóa thành phần không còn dùng;
- tách user/site trên shared hosting hoặc VPS, giới hạn quyền ghi;
- đặt file permission theo process thật, không dùng 777;
- tắt file editor nếu không cần bằng DISALLOW_FILE_EDIT;
- đánh giá XML-RPC theo nhu cầu, rate limit và log thay vì tắt theo phong trào;
- backup ngoài server và thử restore định kỳ;
- cảnh báo khi có PHP file mới, admin mới, cron mới hoặc thay đổi checksum;
- giữ access log đủ lâu để điều tra sau này.
WordPress nhấn mạnh update, backup, quyền truy cập và khả năng phát hiện thay đổi trong Hardening WordPress. Nếu quy trình backup của bạn còn thủ công, có thể xem bài backup dữ liệu server/website như một điểm bắt đầu để kiểm tra khả năng restore. Bài học quan trọng là chuẩn bị trước khi có incident, không phải đợi thấy spam rồi mới bắt đầu nghĩ tới backup.
Case này không chứng minh điều gì?
Đây là phần cần giữ lại để bài không biến thành một câu chuyện chắc chắn quá mức:
- promo.php cho thấy capability sau khi xâm nhập, không tự nói lên initial access;
- watermark hoặc tên nhóm trong file không đủ để attribution;
- file plugin có CVE không có nghĩa plugin đó là cửa vào của case;
- User-Agent/Referer check không xác nhận request đến từ Googlebot thật;
- một lần wp core verify-checksums không chứng minh toàn hệ thống sạch;
- xóa file spam không tự làm credential đã lộ trở nên an toàn;
- không có log thì không nên ghi phần trăm, thời gian tồn tại hoặc số site bị ảnh hưởng.
Viết rõ giới hạn không làm case yếu đi. Ngược lại, nó cho người đọc biết phần nào có thể kiểm tra lại và phần nào chỉ là giả thuyết đang chờ evidence.
FAQ
Xóa promo.php là website sạch chưa?
Chưa. Webshell có thể có bản sao ở nơi khác, persistence trong cron/database, hoặc credential đã bị lấy. Xóa file chỉ là một hành động containment nhỏ, không phải kết luận cleanup.
Có nên tắt XML-RPC ngay không?
Nếu site không dùng XML-RPC, đó có thể là một lớp giảm surface. Nhưng filter xmlrpc_enabled không tắt mọi XML-RPC request. Kiểm tra nhu cầu, method đang dùng, rate limit và log trước khi thay đổi.
Cài lại WordPress core có đủ không?
Không. Core chỉ là một lớp. Plugin, theme, uploads, database, cron, .htaccess, user hosting và máy developer đều có thể giữ persistence hoặc credential cũ.
Làm sao biết Google đang thấy nội dung khác mình?
Dùng URL Inspection và Security Issues trong Search Console, đối chiếu raw response/log và kiểm tra từ môi trường an toàn. Đừng chỉ mở URL nghi nhiễm bằng trình duyệt chính rồi kết luận “không thấy gì”.
Khi nào nên gọi người xử lý incident?
Khi bạn không có backup sạch, không đọc được access/auth log, site chứa dữ liệu khách hàng, attacker còn hoạt động, hoặc nhiều site dùng chung server. Một lần rebuild có kiểm soát thường ít rủi ro hơn nhiều lần xóa file theo cảm giác.
Kết luận: giữ evidence trước khi cleanup
Parasite SEO không cần làm trang chủ sập mới gây thiệt hại. Chỉ cần một webshell giữ quyền ghi, một file bị thay, một sitemap dẫn crawler và một lớp cloaking là domain đã có thể bị dùng cho mục đích khác mà team không hay biết.
Điều đáng giữ lại từ case này không phải tên file hay tên nhóm. Đó là phương pháp: bắt đầu từ triệu chứng, giữ evidence, phân biệt quan sát với suy luận, rebuild từ nguồn sạch và xác nhận lại với Google sau khi xử lý.
Nếu site của bạn đang xuất hiện URL lạ, redirect theo nguồn truy cập hoặc file PHP không rõ nguồn gốc, gửi URL và mô tả triệu chứng sau khi che credential, token và IP riêng. Mình sẽ xem sơ bộ phạm vi cần audit trước, rồi mới đề xuất bước tiếp theo.