Jetpack이라고 적힌 요청 중 Jetpack은 없었다
워드프레스 보안 글에는 거의 빠지지 않고 나오는 항목이 있습니다. xmlrpc.php를 막으라는 것입니다. 저도 그렇게 적어 두었습니다. 그러면서 “다만 Jetpack 같은 서비스를 쓰고 있다면 막기 전에 확인하라”는 단서를 달았습니다. 맞는 말처럼 보였는데, 그 확인을 실제로 해 본 적은 없었습니다. 서버 접속 기록 35일치를 열어서 그 조언을 제 서버에 대 보았습니다. 두 군데가 틀렸습니다.
먼저, 이게 1순위가 아니었다
35일 동안 쌓인 접속 기록 596만 줄에서 자주 지목되는 경로만 세어 보았습니다. 이 서버 한 대에는 사이트가 여러 개 올라가 있어서 아래 숫자는 서버 전체의 합입니다.
| 요청 경로 | 35일 요청 | 고유 IP | 하루 평균 |
|---|---|---|---|
| .git 관련 | 334,234 | 1,446 | 9,550 |
| .env 관련 | 152,288 | 2,028 | 4,351 |
| xmlrpc.php | 97,562 | 2,380 | 2,788 |
| wp-login.php | 48,463 | 3,987 | 1,385 |
| REST API | 30,207 | 1,549 | 863 |
| wp-cron.php | 3,733 | 28 | 107 |

xmlrpc.php는 3위였습니다. 가장 많이 두드려 맞은 경로는 설정 파일을 찾는 요청이었고, 그쪽이 세 배 넘게 많았습니다. 보안 글에서 차지하는 비중과 실제로 들어오는 양이 같지 않았습니다.
그렇다고 xmlrpc 요청이 적은 것은 아닙니다. 9만 7천 건에 보낸 곳이 2,380군데입니다. 다만 “가장 먼저 막아야 할 곳”이라고 말하려면 근거가 있어야 하는데, 제 서버의 기록은 그렇게 말하고 있지 않았습니다. 같은 일을 .git 접근을 막은 뒤에도 한 번 겪었습니다. 널리 하는 조언이 내 서버에서도 1순위인지는 따로 봐야 합니다.
응답 코드가 전부 정상으로 찍힌다
세면서 한 가지를 더 봤습니다. xmlrpc 요청 97,562건 중 95,388건이 정상 응답으로 기록돼 있었습니다. 97.8퍼센트입니다. 요청 방식은 98.5퍼센트가 POST였습니다.
처음에는 이 숫자를 잘못 읽었습니다. 이 경로는 로그인에 실패해도 정상 응답을 돌려줍니다. 실패했다는 사실을 응답 본문 안에 적어서 보내는 구조이기 때문입니다. 그래서 접속 기록에서는 실패한 시도와 성공한 시도가 똑같이 찍힙니다.
로그인 화면을 두드리는 요청은 실패하면 다른 코드가 남아서 눈에 띕니다. 이쪽은 그렇지 않습니다. 코드만 훑어서는 아무 일도 없는 것처럼 보이고, 반대로 다 뚫린 것처럼 보이기도 합니다. 둘 다 틀립니다. 이 경로에 대해서는 응답 코드가 판단 재료가 못 됩니다.
그리고, 저절로 멎었다
날짜별로 갈라 보니 모양이 평평하지 않았습니다. 9월 초 아흐레에 거의 다 몰려 있었습니다.
| 기간 | 요청 | 비중 | 하루 평균 | 고유 IP |
|---|---|---|---|---|
| 몰린 9일 | 69,494 | 71.2% | 7,722 | 441 |
| 나머지 26일 | 28,068 | 28.8% | 1,080 | 1,979 |
아흐레 동안 하루 7,700건씩 들어오다가 열흘째부터 1,080건으로 떨어졌습니다. 일곱 배 차이입니다. 그 사이에 제가 한 일은 없습니다. 설정을 바꾸지도, 차단을 걸지도 않았습니다. 그냥 멎었습니다.
몰린 기간에 들어온 IP 441개 중에서 그 뒤에도 다시 나타난 것은 40개, 9.1퍼센트였습니다. 나머지는 한 차례 왔다 가고 그만이었습니다. 전체로 봐도 2,380개 중 90.3퍼센트가 하루만 나타났습니다. 다만 한 군데는 예외였습니다. 특정 대역 하나가 14,077건으로 전체의 14.4퍼센트를 차지했습니다. 주소 하나가 아니라 같은 대역 안의 여러 주소가 번갈아 들어온 모양이라, 주소 단위로 막으면 옆 주소로 바꿔 다시 들어옵니다. 특정 상대가 제 사이트를 노리고 계속 붙어 있는 그림이 아니라, 대상을 가리지 않고 한 바퀴 훑고 지나가는 그림입니다.
이게 차단 판단에 주는 영향
차단의 효과를 재려면 전후를 비교해야 하는데, 이렇게 요동치는 값에서는 비교가 잘 안 됩니다. 9월 10일에 차단을 걸었다면 다음 날 요청이 7분의 1로 줄었을 것이고, 저는 차단이 들었다고 적었을 것입니다. 사실은 그 전에 이미 끝난 일이었습니다.
그래서 조치를 넣기 전에 평소 값을 며칠 재 두는 편이 낫습니다. 평소가 얼마인지 모르면 조치 후의 숫자를 해석할 수 없습니다. 조치가 효과가 없었다는 결론도, 효과가 있었다는 결론도 똑같이 못 내립니다.
확인하라고 적어 둔 그 항목이 함정이었다
원래 글에서 차단 전에 확인하라고 적은 것이 Jetpack 같은 외부 서비스였습니다. 이 서비스들이 xmlrpc.php로 들어오기 때문에 막으면 연동이 끊어진다는 이유였습니다. 그래서 요청에 적힌 이름을 세어 보았습니다.
97,562건 중 27,223건, 27.9퍼센트가 Jetpack 또는 WordPress.com이라는 이름을 달고 있었습니다. 여기까지 보고 멈췄다면 결론은 정해져 있었습니다. 네 건 중 한 건이 내가 쓰는 서비스이니 막으면 안 된다는 것입니다.
그런데 요청에 적힌 이름은 보내는 쪽이 아무렇게나 적을 수 있는 값입니다. 그래서 이름 대신 어느 주소에서 왔는지를 봤습니다.
| 확인한 것 | 결과 |
|---|---|
| Jetpack·WordPress.com 이름을 단 요청 | 27,223건 |
| 보낸 고유 IP | 813개 |
| 그 서비스를 운영하는 회사 대역에서 온 것 | 0건 |
한 건도 없었습니다. 813개 주소는 전 세계 가정용·이동통신 대역에 흩어져 있었고, 한 대역이 차지하는 비중이 2퍼센트를 넘는 곳이 없었습니다. 실제 연동 서비스는 운영사가 가진 몇 개 대역에서만 들어옵니다. 수백 군데 가정집에서 들어올 수가 없습니다.
즉 제가 “확인하라”고 적어 둔 그 확인을 글자 그대로 하면, 공격하는 쪽이 적어 놓은 이름을 보고 차단을 포기하게 됩니다. 이름을 빌려 쓰면 차단을 피할 수 있다는 것을 상대도 알고 있는 셈입니다.
이름 대신 무엇을 볼 것인가
판단 기준을 바꿔야 합니다. 요청에 적힌 이름은 참고만 하고, 실제로 그 서비스를 쓰고 있는지는 내 쪽에서 확인합니다.
| 확인 방법 | 믿을 수 있나 |
|---|---|
| 요청에 적힌 이름 | 아니오 — 보내는 쪽이 정한다 |
| 관리 화면의 연동 플러그인 목록 | 예 — 내 설치 상태다 |
| 보낸 주소가 운영사 대역인지 | 예 — 바꿔 적기 어렵다 |
| 연동 서비스 쪽 접속 기록 | 예 — 그쪽이 남긴 것이다 |
가장 빠른 것은 첫 줄이 아니라 두 번째 줄입니다. 플러그인 목록을 열어서 외부 연동이 있는지 보면 몇 초면 끝납니다. 연동을 안 쓰고 있다면 로그에 그 이름이 몇 만 건 찍혀 있든 그건 내 서비스가 아닙니다.
그래서 막을 것인가
막는 방법 자체는 간단합니다. 웹 서버 설정이나 .htaccess에서 그 파일로 들어오는 요청을 거절하면 됩니다. 파일을 지우는 방법은 권하지 않습니다. 워드프레스를 업데이트하면 다시 생기고, 그때부터는 막혀 있다고 생각하는 상태로 열려 있게 됩니다. 접근을 막아 두면 업데이트와 무관하게 유지됩니다.
다만 막는 순서는 뒤로 미뤘습니다. 제 서버는 아직 이 경로를 열어 두고 있습니다. 이유는 두 가지입니다. 하나는 평소치가 하루 1,080건이고 응답이 가벼워서 지금 당장 부담이 되지 않는다는 것이고, 다른 하나는 더 많이 들어오는 경로가 따로 있다는 것입니다. 하루 9,550건이 들어오는 쪽을 두고 2,788건 쪽을 먼저 손보는 것은 순서가 아닙니다.
조건은 정해 두었습니다. 평소치가 지금의 몇 배로 올라가거나, 응답 시간이 눈에 띄게 밀리거나, 연동 서비스를 하나도 안 쓰게 되면 그때 막습니다. 특히 마지막 조건이면 망설일 이유가 없습니다. 쓰지 않는 입구는 열어 둘 까닭이 없습니다.
막기로 했다면 설정을 넣은 뒤에 반드시 응답 코드를 확인해야 합니다. 요청을 보내 보고 거절되는지 보고, 서버 기록에서도 실제로 거절로 남는지 봅니다. 설정 파일을 고쳤는데 다른 설정이 먼저 걸려서 그대로 통과하는 경우가 있습니다.
정리
세 가지가 남았습니다. 첫째, 널리 하는 조언이 내 서버에서도 1순위인지는 숫자로 확인해야 합니다. 저는 3순위였습니다. 둘째, 조치 전에 평소 값을 재 둬야 합니다. 안 그러면 저절로 멎은 것을 내가 막았다고 적게 됩니다. 셋째, 요청에 적힌 이름으로 상대를 판단하면 안 됩니다. 제 경우 그 이름을 그대로 믿었다면 차단을 포기했을 것이고, 포기하게 만든 근거는 전부 거짓이었습니다.
숫자에 대해서는 단서를 하나 붙여 둡니다. 서버 한 대의 35일치이고 여러 사이트가 섞여 있습니다. 다른 서버에서는 순위가 다르게 나올 수 있습니다. 그러니 이 글의 결론은 “xmlrpc를 막아라” 또는 “막지 마라”가 아니라, 자기 기록을 열어 보라는 쪽입니다. 저도 그걸 안 하고 글을 쓴 적이 있습니다.