워드프레스 XML-RPC: 비활성화 방법 3가지 & xmlrpc.php 공격 판별 방법 | Jetpack 예외

워드프레스 웹사이트 접근 기록에 xmlrpc.php 요청이 같은 IP에서 수백 건씩 이어진다면 워드프레스 XML-RPC를 노린 비밀번호 대입입니다. 워드프레스 사이트 여러 개를 운영하는 Nginx + Apache 구성 서버의 하루 접근 기록 8,624건을 분류하고, 비활성화 방법 세 가지를 직접 적용해 응답 코드를 확인한 결과, 어느 층에서 막아야 PHP 실행까지 줄어드는지 정리했습니다.
XML-RPC는 HTTP로 XML 문서를 주고받아 워드프레스 기능을 원격으로 호출하는 규약이며, 워드프레스 3.5부터는 설정 화면의 켜고 끄는 항목이 없어져 설치 직후부터 켜진 상태로 운영됩니다.
1️⃣ xmlrpc.php의 역할과 사용하는 곳
REST API가 생긴 뒤로 [xmlrpc.php]를 더 이상 사용되지 않는 파일로 여기기 쉽지만, Jetpack은 지금도 이 파일을 통해 WordPress.com과 사이트를 연결합니다. Jetpack 공식 문서는 XML-RPC 연결에 의존한다고 명시하고 있으며, XML-RPC를 기본 차단하는 호스팅에서는 Jetpack 연결 자체가 실패합니다. 요즘도 사무실에 팩스 번호 하나는 남겨 두는 것과 같습니다.
워드프레스 7.1 기준으로 이 파일이 받는 메서드는 77개이며, 글과 미디어, 댓글을 다루는 wp 계열 50개와 옛 블로그 규약(MetaWeblog, MovableType, Blogger) 23개, 핑백 2개, 시험용 demo 2개로 구성됩니다.
wp 계열은 요청 본문에 담긴 사용자명과 비밀번호로 로그인하므로 로그인 화면을 거치지 않습니다. 글 작성과 수정, 미디어 업로드, 댓글 관리가 모두 이 계열이며, 옛 블로그 규약 23개는 다른 블로그 도구와 같은 방식으로 글을 주고받기 위해 남겨 둔 메서드입니다.
demo.sayHello는 워드프레스 XML-RPC에 들어 있는 시험용 호출입니다. 보내면 [Hello!] 한 마디만 돌려주는데, 로그인이 필요 없어 XML-RPC가 동작하는지 확인하는 용도로 사용합니다. 짝으로 두 수를 더해 돌려주는 demo.addTwoNumbers가 있습니다.
모두 POST로 XML 본문을 보내야 응답하며, 브라우저로 주소를 열면 [XML-RPC server accepts POST requests only.] 문구와 함께 405가 돌아옵니다.
핑백도 이 파일의 몫입니다. 글을 발행하면 본문에 링크한 사이트의 xmlrpc.php로 핑백을 보내고, 받는 쪽 서버는 보낸 주소를 직접 열어 링크가 실제로 있는지 확인합니다.
2️⃣ 접근 기록으로 보는 xmlrpc.php 공격
비밀번호 대입은 로그인 화면을 거치지 않고 [xmlrpc.php]로 들어옵니다. 워드프레스 사이트 여러 개를 운영하는 Nginx + Apache 구성 서버의 10월 14일 0시부터 20시까지 접근 기록에서 POST /xmlrpc.php 요청은 8,624건, IP는 72개였고, 그중 19개 IP가 정확히 300건씩 보냈습니다.
한 IP는 14시 9분부터 37분까지 28분 동안 300건을 보냈으며, 5~6초 간격이 끝까지 일정했습니다. 요청 8,524건은 User-Agent에 Jetpack 또는 WordPress.com을 적고 들어왔고, [Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)]처럼 실제 Jetpack 형식을 흉내 낸 값을 같은 IP가 여러 개 번갈아 사용했습니다.
같은 날 14시 전까지는 보안 플러그인의 XML-RPC 끄기가 이 요청을 홈으로 302 돌려보냈고, 14시 이후 Nginx에서 차단한 뒤에는 403으로 끝났습니다. 302는 본문이 0바이트라도 요청마다 PHP가 실행된 결과이고, 403은 PHP를 거치지 않은 응답입니다.
3️⃣ system.multicall과 비밀번호 대입
[system.multicall]로 요청 한 번에 비밀번호 수백 개를 시험한다는 설명이 많지만, 워드프레스는 한 요청 안에서 로그인이 한 번 실패하면 나머지 호출의 비밀번호를 대조하지 않습니다. 첫 호출만 wp_authenticate를 거치고, 이후 호출은 검사 없이 같은 오류 [Incorrect username or password.]로 응답합니다.
호출 수만큼 응답은 그대로 돌아오므로, 틀린 비밀번호 100개를 담은 요청 하나에 23KB 응답으로 오류 100개가 0.12초 만에 돌아옵니다. 같은 요청 안에서 맞는 비밀번호를 뒤에 두면 앞 호출의 실패 때문에 함께 거부되고, 앞에 두면 그 호출만 성공합니다. 비밀번호 대조는 한 번이지만 XML 해석과 워드프레스 로딩은 요청마다 반복되므로, 이 공격의 서버 부담은 로그인 검사가 아니라 요청 수 자체에서 발생합니다.
4️⃣ XML-RPC 비활성화 방법 3가지와 응답 코드
플러그인에서 XML-RPC를 꺼도 [xmlrpc.php] 요청은 PHP까지 들어옵니다. 필터로 끈 상태에서 호출 100개짜리 요청을 보내도 HTTP 200에 오류 100개(24KB)가 그대로 돌아왔습니다. 어느 층에서 거부하느냐에 따라 방법은 셋으로 나뉘며, 응답 코드가 각각 다릅니다.
1. 코드로 xmlrpc_enabled 필터 끄기
테마의 functions.php에 [xmlrpc_enabled] 필터를 false로 돌리는 add_filter 한 줄을 넣으면 됩니다. 이 상태에서 로그인이 필요한 호출은 [XML-RPC services are disabled on this site.] 오류와 HTTP 405로 거부되지만, 로그인이 필요 없는 시험용 호출 demo.sayHello는 여전히 200으로 응답합니다.
2. 플러그인 설정으로 끄기: Perfmatters·Hide My WP
Perfmatters 및 Hide My WP 등의 플러그인에서 Disable XML-RPC 설정으로 비활성화하는 방법입니다. Hide My WP는 위와 같은 필터 방식이라 응답도 같지만, Perfmatters는 같은 필터에 더해 [xmlrpc.php] 직접 요청을 403으로 끝내고 핑백 헤더와 핑백 링크까지 제거하므로, 끄고 나면 GET과 POST 모두 403으로 응답합니다. 다만 403을 내기 위해 워드프레스가 init 단계까지 실행되므로, 응답까지 0.13~0.15초로 열려 있을 때와 같은 시간이 걸리고 PHP 부담은 남습니다.
3. 웹 서버에서 차단: Nginx·Apache
Nginx와 Apache 어느 쪽이든 PHP 앞에서 요청을 거부하므로, 플러그인 방식과 달리 워드프레스가 실행되지 않습니다.
# Nginx — server 블록 안, PHP 처리 location 보다 위에
location = /xmlrpc.php {
deny all;
}이 location 블록은 PHP 처리 location보다 위에 두며, [xmlrpc.php]와 정확히 일치하는 요청을 Nginx가 바로 거부합니다. 넣은 뒤에는 설정을 점검하고 다시 읽어야 반영됩니다.
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsystemctl reload nginx다시 읽은 뒤부터 GET과 POST가 PHP까지 가지 않고 403으로 끝나며, 응답 본문은 Nginx 기본 403 페이지 153바이트이고 응답까지 0.02초입니다.
# Apache — 사이트 루트 .htaccess (Apache 2.4)
<Files xmlrpc.php>
Require all denied
</Files>Apache만 사용하는 서버는 사이트 루트의 .htaccess에 위 Files 지시어를 넣으면 바로 적용되며, Apache 기본 403 페이지(199바이트)가 같은 0.02초에 돌아옵니다.
curl -s -o /dev/null -w '%{http_code}' -X POST https://example.com/xmlrpc.php -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>'403위 명령은 시험용 호출 demo.sayHello를 POST로 보내 응답 코드를 확인하며, 웹 서버에서 차단한 뒤에는 403이 돌아옵니다. [-o /dev/null]과 [-w] 옵션을 빼고 보내면 본문이 보이는데, [Hello!]가 오면 열린 상태이고, 필터로만 끈 사이트도 같은 응답이 오므로 로그인 호출까지 보내야 405를 확인할 수 있습니다.
5️⃣ 끄면 안 되는 경우와 대안
웹 서버에서 통째로 막는 방법은 효과가 가장 크지만, Jetpack으로 WordPress.com과 연결한 사이트에는 맞지 않습니다. Jetpack은 XML-RPC 연결에 의존하며, WP Engine처럼 XML-RPC를 기본 차단하는 호스팅에서는 Jetpack 쪽 안내에 따라 예외를 열어야 연결됩니다. 핑백을 받으려는 사이트도 마찬가지입니다.
이런 사이트는 Jetpack이 공개하는 IP 목록(jetpack.com/ips-v4.txt)만 허용하고 나머지를 거부하는 방식으로 좁힙니다. Nginx의 location 블록 안에 allow 줄을 IP 대역만큼 넣고 마지막에 deny all을 두면 됩니다.
완전히 막을 수 없다면 로그인하지 않은 요청에 분당 횟수 제한을 거는 방법이 남습니다. 설정은 wp-admin 보안 글의 Nginx 속도 제한 항목과 같으며, 로그인 쿠키가 없는 요청만 세므로 로그인한 관리자 작업에는 영향이 없습니다.
🔢 FAQ & 추천 콘텐츠










ℹ️ 제휴 안내
본 사이트의 콘텐츠에는 제휴 링크가 포함되어 있습니다. 방문자가 이 링크를 통해 상품 또는 서비스를 구매하면 본 사이트는 판매처로부터 수수료를 지급받습니다. 이 과정에서 구매자가 지불하는 금액(이벤트 할인 시 금액이 내려갑니다↓)은 오르지 않습니다. 게시된 가격·할인·재고 정보는 작성 시점 기준이며 실제와 다를 수 있으므로, 구매 전 판매처에서 최종 확인하시기 바랍니다. 상품 선정과 평가는 자체 기준에 따라 작성되며, 수수료 지급 여부가 소개 순서나 평가 내용에 영향을 주지 않습니다.