WordPress XML-RPC: 3 cách vô hiệu hóa & cách nhận biết tấn công xmlrpc.php | Ngoại lệ Jetpack

Nếu log truy cập của website WordPress có hàng trăm yêu cầu xmlrpc.php liên tiếp từ cùng một IP, đó là tấn công dò mật khẩu nhắm vào WordPress XML-RPC. Bài viết phân loại 8.624 dòng log truy cập trong một ngày của server Nginx + Apache chạy nhiều website WordPress, trực tiếp áp dụng ba cách vô hiệu hóa và kiểm tra mã phản hồi, từ đó tổng hợp xem phải chặn ở lớp nào thì mới giảm được cả việc chạy PHP.
XML-RPC là giao thức trao đổi tài liệu XML qua HTTP để gọi các chức năng của WordPress từ xa; từ WordPress 3.5, mục bật/tắt trong màn hình cài đặt đã bị bỏ, nên XML-RPC chạy ở trạng thái bật ngay sau khi cài đặt.
1️⃣ Vai trò của xmlrpc.php và nơi sử dụng
Từ khi có REST API, dễ nghĩ rằng [xmlrpc.php] là tệp không còn ai dùng, nhưng đến nay Jetpack vẫn kết nối website với WordPress.com qua tệp này. Tài liệu chính thức của Jetpack ghi rõ là phụ thuộc vào kết nối XML-RPC, và trên hosting chặn XML-RPC theo mặc định thì ngay cả việc kết nối Jetpack cũng thất bại. Cũng giống như ngày nay nhiều văn phòng vẫn giữ lại một số fax.
Tính đến WordPress 7.1, tệp này nhận 77 phương thức: 50 phương thức họ wp xử lý bài viết, media và bình luận, 23 phương thức của các giao thức blog cũ (MetaWeblog, MovableType, Blogger), 2 phương thức pingback và 2 phương thức demo để thử nghiệm.
Họ wp đăng nhập bằng tên người dùng và mật khẩu nằm trong phần thân yêu cầu, nên không đi qua màn hình đăng nhập. Viết và sửa bài, tải lên media, quản lý bình luận đều thuộc họ này; còn 23 phương thức của các giao thức blog cũ được giữ lại để trao đổi bài viết theo cùng cách với các công cụ blog khác.
demo.sayHello là lệnh gọi thử có sẵn trong WordPress XML-RPC. Gửi đi thì nó chỉ trả về đúng một câu [Hello!]; vì không cần đăng nhập nên được dùng để kiểm tra XML-RPC có hoạt động hay không. Đi cặp với nó là demo.addTwoNumbers, cộng hai số rồi trả về kết quả.
Tất cả chỉ phản hồi khi phần thân XML được gửi bằng POST; mở địa chỉ bằng trình duyệt thì sẽ nhận về thông báo [XML-RPC server accepts POST requests only.] cùng mã 405.
Pingback cũng là việc của tệp này. Khi xuất bản bài viết, WordPress gửi pingback tới xmlrpc.php của các website được liên kết trong nội dung, và server bên nhận tự mở địa chỉ gửi đến để kiểm tra liên kết có thật hay không.
2️⃣ Tấn công xmlrpc.php qua log truy cập
Tấn công dò mật khẩu không đi qua màn hình đăng nhập mà vào qua [xmlrpc.php]. Trong log truy cập từ 0:00 đến 20:00 ngày 14 tháng 10 của server Nginx + Apache chạy nhiều website WordPress, có 8.624 yêu cầu POST /xmlrpc.php đến từ 72 IP, trong đó 19 IP gửi đúng 300 yêu cầu mỗi IP.
Một IP đã gửi 300 yêu cầu trong 28 phút, từ 14:09 đến 14:37, với khoảng cách 5–6 giây đều đặn đến cuối. 8.524 yêu cầu ghi Jetpack hoặc WordPress.com trong User-Agent, và cùng một IP luân phiên dùng nhiều giá trị bắt chước định dạng Jetpack thật, chẳng hạn [Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)].
Cùng ngày, trước 14:00, cài đặt tắt XML-RPC của plugin bảo mật trả các yêu cầu này về trang chủ bằng 302; sau 14:00, khi đã chặn ở Nginx, chúng kết thúc bằng 403. 302 nghĩa là PHP đã chạy cho từng yêu cầu dù phần thân chỉ 0 byte, còn 403 là phản hồi không đi qua PHP.
3️⃣ system.multicall và dò mật khẩu
Nhiều bài giải thích rằng [system.multicall] cho phép thử hàng trăm mật khẩu trong một yêu cầu, nhưng trong một yêu cầu, chỉ cần đăng nhập thất bại một lần là WordPress không đối chiếu mật khẩu của các lệnh gọi còn lại nữa. Chỉ lệnh gọi đầu tiên đi qua wp_authenticate; các lệnh gọi sau được trả lời bằng cùng lỗi [Incorrect username or password.] mà không qua kiểm tra.
Vì số phản hồi trả về vẫn bằng số lệnh gọi, một yêu cầu chứa 100 mật khẩu sai sẽ nhận về 100 lỗi trong phản hồi 23KB chỉ sau 0,12 giây. Trong cùng một yêu cầu, nếu đặt mật khẩu đúng ở sau thì nó cũng bị từ chối theo vì lệnh gọi phía trước đã thất bại; nếu đặt ở trước thì chỉ lệnh gọi đó thành công. Mật khẩu chỉ được đối chiếu một lần, nhưng việc phân tích XML và tải WordPress lặp lại ở mỗi yêu cầu, nên tải cho server của kiểu tấn công này đến từ chính số lượng yêu cầu chứ không phải từ việc kiểm tra đăng nhập.
4️⃣ 3 cách vô hiệu hóa XML-RPC và mã phản hồi
Dù đã tắt XML-RPC bằng plugin, yêu cầu [xmlrpc.php] vẫn vào tới PHP. Khi đã tắt bằng filter, gửi một yêu cầu chứa 100 lệnh gọi vẫn nhận về nguyên HTTP 200 kèm 100 lỗi (24KB). Tùy lớp nào từ chối yêu cầu mà có ba cách, và mỗi cách cho mã phản hồi khác nhau.
1. Tắt filter xmlrpc_enabled bằng code
Chỉ cần thêm vào functions.php của theme một dòng add_filter trả filter [xmlrpc_enabled] về false. Ở trạng thái này, các lệnh gọi cần đăng nhập bị từ chối với lỗi [XML-RPC services are disabled on this site.] và HTTP 405, nhưng lệnh gọi thử demo.sayHello, vốn không cần đăng nhập, vẫn trả lời bằng 200.
2. Tắt trong cài đặt plugin: Perfmatters · Hide My WP
Đây là cách vô hiệu hóa bằng cài đặt Disable XML-RPC trong các plugin như Perfmatters hay Hide My WP. Hide My WP dùng cùng cách filter như trên nên phản hồi cũng giống, còn Perfmatters, ngoài filter đó, còn kết thúc các yêu cầu trực tiếp tới [xmlrpc.php] bằng 403 và gỡ cả header pingback lẫn liên kết pingback, nên sau khi tắt, cả GET và POST đều trả lời 403. Tuy nhiên, để trả về 403, WordPress vẫn chạy đến giai đoạn init, nên thời gian phản hồi là 0,13–0,15 giây, bằng lúc còn mở, và tải PHP vẫn còn.
3. Chặn ở web server: Nginx · Apache
Dù là Nginx hay Apache, yêu cầu đều bị từ chối trước khi tới PHP, nên khác với cách dùng plugin, WordPress không chạy.
# Nginx — trong khối server, đặt trên location xử lý PHP
location = /xmlrpc.php {
deny all;
}Khối location này đặt trên location xử lý PHP, và Nginx từ chối ngay các yêu cầu khớp chính xác với [xmlrpc.php]. Sau khi thêm, phải kiểm tra cấu hình rồi tải lại thì mới có hiệu lực.
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsystemctl reload nginxTừ sau khi tải lại, GET và POST kết thúc bằng 403 mà không tới PHP; phần thân phản hồi là trang 403 mặc định của Nginx, 153 byte, và thời gian phản hồi là 0,02 giây.
# Apache — .htaccess ở thư mục gốc của website (Apache 2.4)
<Files xmlrpc.php>
Require all denied
</Files>Với server chỉ dùng Apache, đặt chỉ thị Files ở trên vào .htaccess ở thư mục gốc của website là áp dụng ngay, và trang 403 mặc định của Apache (199 byte) được trả về trong cùng 0,02 giây.
curl -s -o /dev/null -w '%{http_code}' -X POST https://example.com/xmlrpc.php -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>'403Lệnh trên gửi lệnh gọi thử demo.sayHello bằng POST để kiểm tra mã phản hồi; sau khi chặn ở web server thì sẽ nhận về 403. Bỏ các tùy chọn [-o /dev/null] và [-w] rồi gửi thì sẽ thấy phần thân: nhận được [Hello!] là đang mở, nhưng website chỉ tắt bằng filter cũng nhận cùng phản hồi đó, nên phải gửi thêm lệnh gọi đăng nhập mới xác nhận được 405.
5️⃣ Khi không nên tắt và phương án thay thế
Chặn toàn bộ ở web server là cách hiệu quả nhất, nhưng không phù hợp với website kết nối với WordPress.com qua Jetpack. Jetpack phụ thuộc vào kết nối XML-RPC, và trên hosting chặn XML-RPC theo mặc định như WP Engine, phải mở ngoại lệ theo hướng dẫn của Jetpack thì mới kết nối được. Website muốn nhận pingback cũng vậy.
Những website này thu hẹp phạm vi bằng cách chỉ cho phép danh sách IP mà Jetpack công bố (jetpack.com/ips-v4.txt) và từ chối phần còn lại. Chỉ cần thêm vào khối location của Nginx một dòng allow cho mỗi dải IP và đặt deny all ở cuối.
Nếu không thể chặn hoàn toàn, vẫn còn cách đặt giới hạn số lần mỗi phút cho các yêu cầu chưa đăng nhập. Cách thiết lập giống mục giới hạn tốc độ Nginx trong bài Bảo mật wp-admin — vì chỉ đếm các yêu cầu không có cookie đăng nhập nên không ảnh hưởng đến thao tác quản trị khi đã đăng nhập.
🔢 FAQ & nội dung đề xuất










ℹ️ Thông báo tiếp thị liên kết
Nội dung trên trang này có chứa liên kết affiliate (tiếp thị liên kết). Khi khách truy cập mua sản phẩm hoặc dịch vụ qua các liên kết này, trang sẽ nhận hoa hồng từ bên bán. Trong quá trình đó, số tiền người mua phải trả (sẽ giảm khi có chương trình khuyến mãi ↓) không hề tăng lên. Giá, khuyến mãi và tình trạng hàng được đăng theo thời điểm viết bài và có thể khác với thực tế, vì vậy hãy kiểm tra lại lần cuối với bên bán trước khi mua. Việc chọn và đánh giá sản phẩm dựa trên tiêu chí riêng của trang; việc có nhận hoa hồng hay không không ảnh hưởng đến thứ tự giới thiệu hay nội dung đánh giá.