워드프레스 보안 점검을 하다가 깜짝 놀란 게 하나 있었습니다. 로그인하지 않은 사람도 주소창에 몇 글자만 치면 관리자 로그인 아이디를 알아낼 수 있다는 점이었어요. 비밀번호만 강하면 괜찮다고 생각하기 쉽지만, 아이디를 아는 순간 무차별 대입 공격(Brute Force)은 절반이 끝난 셈입니다.
이번 글에서는 제 블로그에서 실제로 확인한 아이디 노출 경로 5곳과, 플러그인 없이 mu-plugin 파일 하나로 모두 막은 방법을 전후 비교와 함께 정리했습니다.
내 워드프레스도 아이디가 노출되고 있을까? (1분 확인법)
아래 명령어에서 example.com을 본인 도메인으로 바꿔서 실행해 보세요. 브라우저 주소창에 직접 넣어봐도 됩니다.
# 1) REST API 사용자 목록
curl -s https://example.com/wp-json/wp/v2/users
# 2) 고유주소를 안 쓰는 형태의 REST API
curl -s "https://example.com/?rest_route=/wp/v2/users"
# 3) 작성자 번호로 찔러보기 (리다이렉트 주소에 아이디가 나옴)
curl -sI "https://example.com/?author=1" | grep -i location
결과에 "slug":"admin" 같은 값이 보이거나, Location: https://example.com/author/admin/처럼 작성자 주소로 넘어간다면 로그인 아이디가 그대로 노출되고 있는 것입니다. 워드프레스는 기본적으로 작성자 slug(user_nicename)를 로그인 아이디와 똑같이 만들기 때문이에요.
아이디가 새어 나가는 경로 5곳
제 블로그를 점검해 보니 생각보다 경로가 많았습니다.
- REST API –
/wp-json/wp/v2/users,/wp-json/wp/v2/users/1,/?rest_route=/wp/v2/users - 작성자 번호 열거 –
/?author=1,/?author=2… 를 차례로 넣으면/author/아이디/로 리다이렉트 - 작성자 아카이브 페이지 –
/author/아이디/페이지 자체 - 글마다 붙는 작성자 링크 – 테마가 글 상단에 “작성자: ○○”로
/author/아이디/링크를 출력 - 사이트맵 – 워드프레스 기본
wp-sitemap-users-1.xml, Yoast SEO의author-sitemap.xml
여기에 하나 더, 화면에 보이는 작성자 표시 이름이 아이디와 같으면 코드를 아무리 막아도 소용이 없습니다. 이건 마지막에 따로 다루겠습니다.
해결 방법: mu-plugin 파일 하나로 막기
보안 플러그인을 설치해도 되지만, 이 정도 기능은 짧은 코드로 충분합니다. 일반 플러그인이 아니라 mu-plugin(Must-Use Plugin)으로 넣으면 관리자 화면에서 실수로 비활성화할 일도 없고, 별도 활성화 없이 바로 적용됩니다.
wp-content/mu-plugins/ 폴더(없으면 만들어 주세요)에 block-user-enumeration.php 파일을 아래 내용으로 만듭니다.
<?php
/**
* Plugin Name: Block User Enumeration
* Description: 비로그인 방문자에게 로그인 아이디(작성자 slug)가 노출되지 않도록 차단
*/
if ( ! defined( 'ABSPATH' ) ) exit;
// 1) REST API /wp/v2/users 를 비로그인 사용자에게 숨김 (?rest_route= 경로 포함)
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( is_user_logged_in() ) return $endpoints;
foreach ( array_keys( $endpoints ) as $route ) {
if ( strpos( $route, '/wp/v2/users' ) === 0 ) unset( $endpoints[ $route ] );
}
return $endpoints;
} );
// 2) ?author=N 열거 및 /author/아이디/ 아카이브 → 홈으로
add_action( 'template_redirect', function () {
if ( is_user_logged_in() ) return;
if ( isset( $_GET['author'] ) || is_author() ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
}, 1 );
// 3) 글/테마에 찍히는 작성자 링크에서 아이디 제거
add_filter( 'author_link', function ( $link ) {
return is_admin() ? $link : home_url( '/' );
} );
// 4) 사이트맵에서 작성자 제외 (워드프레스 기본 + Yoast SEO)
add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) {
return 'users' === $name ? false : $provider;
}, 10, 2 );
add_filter( 'wpseo_sitemap_exclude_author', '__return_empty_array' );
add_filter( 'wpseo_sitemaps_providers', function ( $providers ) {
return array_filter( $providers, function ( $p ) {
return ! ( $p instanceof WPSEO_Author_Sitemap_Provider );
} );
} );
// 5) oEmbed 응답의 author_url 제거
add_filter( 'oembed_response_data', function ( $data ) {
unset( $data['author_url'] );
return $data;
} );
서버에 SSH로 접속할 수 있다면, 파일을 만든 뒤 아래처럼 문법 검사와 권한 설정까지 해두면 안전합니다.
# 문법 오류 확인 (오류가 있으면 사이트 전체가 멈출 수 있으니 꼭 확인)
php -l block-user-enumeration.php
# 웹서버 사용자 소유로 설치 (Ubuntu + nginx/Apache 기준 www-data)
sudo install -o www-data -g www-data -m 644 block-user-enumeration.php /var/www/wordpress/wp-content/mu-plugins/
코드 포인트 정리
- 로그인한 사용자는 예외로 두었습니다. 블록 편집기(구텐베르크)는 작성자 선택 등에
/wp/v2/users를 쓰기 때문에, 관리자까지 막으면 글쓰기 화면이 오작동할 수 있어요. - REST API를 통째로 막는 방법도 있지만, 그러면 Jetpack이나 각종 연동 기능이 깨질 수 있습니다. users 경로만 골라서 숨기는 쪽이 부작용이 적습니다.
?rest_route=형태도 같은rest_endpoints필터를 거치기 때문에 함께 막힙니다. nginx에서/wp-json/wp/v2/users경로만 막으면 이 우회 경로가 남는다는 점도 기억해 두세요.
적용 후 꼭 해야 할 것: 캐시 비우기
WP Fastest Cache 같은 캐시 플러그인을 쓰고 있다면, 코드를 넣어도 예전에 저장된 페이지가 그대로 나갑니다. 저도 처음에 “왜 안 막히지?” 하고 한참 봤는데, 캐시된 HTML에 작성자 링크가 그대로 남아 있었어요. 캐시 플러그인 메뉴에서 전체 캐시 삭제를 한 번 해주세요.
적용 전후 비교
캐시를 비운 뒤 처음의 확인 명령어를 다시 실행한 결과입니다.
| 경로 | 적용 전 | 적용 후 |
|---|---|---|
| /wp-json/wp/v2/users | 200, 아이디 노출 | 404 |
| /?rest_route=/wp/v2/users | 200, 아이디 노출 | 404 |
| /wp-json/wp/v2/users/1 | 200, 아이디 노출 | 404 |
| /?author=1 | /author/아이디/ 로 이동 | 홈으로 이동 |
| /author/아이디/ | 작성자 페이지 열림 | 홈으로 이동 |
| 글의 작성자 링크 | /author/아이디/ | 홈 주소 |
| 작성자 사이트맵 | 아이디 포함 | 사이트맵 목록에서 제외 |
홈페이지, 로그인 페이지, 일반 REST API(/wp-json/)는 모두 정상 동작하는 것도 함께 확인했습니다.
마지막 한 가지: 작성자 표시 이름 바꾸기
코드로 링크는 다 숨겼는데, 글 상단에 “작성자: admin”처럼 이름이 그대로 찍혀 있다면 헛수고가 됩니다. 워드프레스는 처음 가입할 때 닉네임과 표시 이름을 로그인 아이디로 채워 두기 때문에, 대부분 이 상태예요.
- 관리자 화면 → 사용자 → 프로필
- 닉네임(필수)을 아이디와 다른 이름으로 변경
- 공개적으로 보일 이름에서 방금 바꾼 닉네임 선택
- 프로필 업데이트 클릭 후, 캐시 한 번 더 비우기
이렇게 바꾸면 화면, RSS 피드, Yoast SEO가 만드는 구조화 데이터(schema)에도 새 이름이 들어갑니다.
정리
- 워드프레스는 기본 설정 그대로면 REST API, ?author=, 작성자 페이지, 사이트맵 등으로 로그인 아이디가 노출됩니다.
- mu-plugin 파일 하나로 비로그인 방문자에게만 이 경로들을 막을 수 있고, 관리자 기능에는 영향이 없습니다.
- 적용 후에는 캐시 비우기, 그리고 작성자 표시 이름 변경까지 해야 완성입니다.
아이디를 숨기는 것만으로 공격을 완전히 막을 수는 없습니다. 강력한 비밀번호와 2단계 인증, 로그인 시도 제한까지 함께 적용하는 것을 권장드려요. 그래도 공격자에게 아이디를 공짜로 알려주지 않는 것만으로도 자동화된 공격의 상당수를 걸러낼 수 있습니다.