워드프레스 REST API가 로그인 없이 내주는 것, 라우트 129개를 직접 확인했다

워드프레스 REST API가 로그인 없이 내주는 것, 라우트 129개를 직접 확인했다

워드프레스는 설치만 하면 REST API가 켜집니다. 플러그인을 붙이지 않아도, 설정을 건드리지 않아도 그렇습니다. 그런데 이 API가 로그인하지 않은 사람에게 무엇까지 보여주는지는 대부분 확인하지 않고 넘어갑니다. 그래서 운영 중인 사이트에서 직접 세어 봤습니다. 라우트가 몇 개인지, 어떤 주소가 인증 없이 열리는지, 열리는 응답에 무엇이 들어 있는지입니다.

먼저 라우트 개수부터

REST API는 주소 하나가 아니라 여러 개의 ‘라우트’ 묶음입니다. 등록된 전체 목록은 다음으로 확인합니다.

wp eval 'echo count( rest_get_server()->get_routes() );'

기본 상태에 가까운 사이트에서 129개가 나왔습니다. 어디에서 온 것인지 묶어 보면 이렇습니다.

네임스페이스 라우트 무엇인가
wp/v2 106개 글·페이지·미디어·사용자 등 기본 데이터
wp-site-health/v1 8개 사이트 상태 검사
wp-abilities/v1 6개 기능 조회
wp-block-editor/v1 4개 편집기 보조
oembed/1.0 3개 외부 삽입
그 외 2개 루트, 일괄 처리

플러그인을 여러 개 쓰는 사이트라면 여기에 더 붙습니다. 중요한 것은 개수가 아니라 이 중 몇 개가 로그인 없이 열리느냐입니다.

인증 없이 열어 보면

로그인하지 않은 상태로 주요 주소를 하나씩 요청했습니다. 결과를 응답 코드와 크기로 정리하면 이렇습니다.

주소 응답 크기 내용
/wp-json/ 200 261,907바이트 전체 라우트 명세
/wp/v2/posts 200 27,341바이트 글 본문 전체
/wp/v2/users 200 1,685바이트 작성자 목록
/wp/v2/media 200 5,016바이트 첨부 파일 경로
/wp/v2/tags 200 6,062바이트 태그 10종
/wp/v2/types 200 5,733바이트 글 유형 11종
/wp/v2/settings 401 162바이트 거부됨
/wp/v2/themes 401 171바이트 거부됨

설정과 테마 정보는 막혀 있습니다. 워드프레스가 민감한 것은 제대로 가리고 있다는 뜻입니다. 문제는 막히지 않은 쪽의 양입니다.

26만 바이트짜리 명세가 그냥 열린다

가장 눈에 띈 것은 /wp-json/ 자체였습니다. 261,907바이트, 약 256KB입니다. 글 하나를 통째로 받는 것보다 열 배 가까이 큽니다.

여기에는 등록된 모든 라우트의 이름과 받을 수 있는 인자, 기본값이 들어 있습니다. 사이트가 어떤 기능을 켜 두었는지가 한 번의 요청으로 드러납니다. 플러그인이 자기 네임스페이스를 등록해 두었다면 그 이름과 버전까지 함께 나옵니다.

이 주소는 자동화 도구가 가장 먼저 확인하는 곳 중 하나입니다. 어떤 플러그인이 깔려 있는지 알면 그다음에 무엇을 시도할지가 정해지기 때문입니다.

작성자 목록에 들어 있는 필드

/wp/v2/users의 응답을 열어 보면 필드가 이렇게 들어 있습니다.

id, name, url, description, link, slug, avatar_urls, meta

id=3  name=길호  slug=kilho
link=https://.../author/kilho/

비밀번호나 이메일은 없습니다. 그런데 slug가 그대로 나옵니다. 워드프레스는 계정을 만들 때 로그인 아이디를 그대로 슬러그로 씁니다. 따로 바꾸지 않았다면 이 값이 곧 아이디입니다.

글을 쓴 적이 있는 사용자만 목록에 나오므로, 글쓴이가 한 명이면 한 명만 나옵니다. 반대로 여러 명이 글을 쓰는 사이트라면 계정 목록이 통째로 공개됩니다.

응답을 줄이는 방법은 막는 것이 아니다

노출을 줄이겠다고 엔드포인트를 막기 전에 확인할 것이 하나 더 있습니다. 같은 주소라도 요청 방식에 따라 응답 크기가 크게 달라집니다. 실제로 재봤습니다.

요청 응답 크기
글 1건, 기본 27,341바이트
글 1건, _fields=id,title,link 276바이트
글 10건, 기본 286,683바이트
글 10건, _fields=id,title,link 2,594바이트

110분의 1로 줄었습니다. 기본 응답에는 본문 HTML, 요약, 각종 링크 묶음이 전부 들어 있기 때문입니다. 제목과 주소만 필요한 화면이라면 이 대부분이 버려집니다.

/wp-json/wp/v2/posts?per_page=10&_fields=id,title,link

외부에서 우리 글 목록을 가져다 쓰는 연동이 있다면 이 한 줄로 전송량과 처리 시간이 함께 줄어듭니다. 서버가 만들어야 할 JSON도 작아집니다.

노출과 성능은 다른 문제다

다만 _fields요청하는 쪽이 정하는 값입니다. 사이트가 강제할 수 없습니다. 즉 이것은 노출을 줄이는 수단이 아니라 내가 만든 연동을 가볍게 만드는 수단입니다. 둘을 섞으면 헛수고를 합니다.

노출을 줄이려면 앞에서 본 것처럼 서버 쪽에서 거부해야 하고, 전송량을 줄이려면 요청 쪽에서 필드를 지정해야 합니다.

응답 헤더가 알려주는 것

헤더도 함께 확인해 두면 오해를 줄일 수 있습니다.

cache-control    no-cache, must-revalidate
x-robots-tag     noindex
x-wp-total       30
x-wp-totalpages  30

세 가지를 읽을 수 있습니다.

첫째, no-cache입니다. REST 응답은 페이지 캐시를 타지 않습니다. 캐시 플러그인을 켜 두었어도 이 요청은 매번 PHP와 데이터베이스를 거칩니다. 외부 연동이 이 주소를 자주 호출하면 서버 부하로 직결됩니다.

둘째, x-robots-tag: noindex입니다. 검색엔진이 이 응답을 색인하지 않도록 워드프레스가 스스로 표시합니다. JSON이 검색 결과에 뜰까 걱정할 필요는 없습니다.

셋째, x-wp-total입니다. 전체 글 수가 헤더에 그대로 담깁니다. 본문을 받지 않아도 사이트 규모를 알 수 있다는 뜻입니다. 숨길 수 있는 값은 아니지만, 이런 정보가 어디에 실려 나가는지는 알고 있는 편이 낫습니다.

줄일 수 있는 것과 없는 것

여기서 흔한 조언이 “REST API를 꺼라”입니다. 하지만 이건 대부분 잘못된 처방입니다. 블록 편집기가 REST API 위에서 동작하기 때문에 전부 막으면 글을 쓸 수 없습니다.

구분해서 봐야 합니다.

대상 어떻게 부작용
users 엔드포인트 비로그인 요청만 차단 거의 없음
작성자 슬러그 아이디와 다르게 지정 없음
/wp-json/ 명세 그대로 두는 편 막으면 편집기·연동이 깨질 수 있음
전체 API 막지 않는다 편집기가 동작하지 않음

사용자 목록만 가리는 것은 필터 하나로 됩니다. 로그인한 사람에게는 그대로 두고, 로그인하지 않은 요청만 거부하는 방식입니다.

add_filter( 'rest_authentication_errors', function ( $result ) {
    if ( ! empty( $result ) ) {
        return $result;
    }
    $route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';
    if ( 0 === strpos( $route, '/wp/v2/users' ) && ! is_user_logged_in() ) {
        return new WP_Error(
            'rest_forbidden',
            '허용되지 않은 요청입니다.',
            [ 'status' => 401 ]
        );
    }
    return $result;
} );

막은 뒤에는 반드시 다시 확인한다

규칙을 넣고 끝내면 안 됩니다. 캐시나 다른 플러그인 때문에 의도대로 동작하지 않는 경우가 있습니다. 같은 방법으로 다시 요청해 응답 코드가 바뀌었는지 봐야 합니다.

curl -s -o /dev/null -w "%{http_code}n" 
  https://내도메인/wp-json/wp/v2/users

401이 나오면 성공이고, 200이면 규칙이 안 걸린 것입니다. 로그인한 브라우저로 열면 여전히 보이는 것이 정상입니다. 편집기가 이 목록을 쓰기 때문입니다.

정리하면

REST API는 켜고 끄는 스위치가 아니라 129개의 문이 달린 벽에 가깝습니다. 전부 잠그면 집에 못 들어가고, 그대로 두면 필요 이상으로 보입니다.

순서는 이렇게 잡는 편이 좋습니다. 먼저 /wp-json/을 열어 자기 사이트에 라우트가 몇 개인지 확인하고, 다음으로 users·media·comments처럼 사람과 파일이 드러나는 주소만 하나씩 요청해 봅니다. 여기서 놀랄 만한 것이 나오지 않으면 그대로 두어도 됩니다. 재보지 않은 채 전부 막는 것이 가장 흔한 실수입니다.

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다