워드프레스는 글을 발행한 사용자의 목록을 기본으로 공개합니다. 로그인하지 않은 채 /wp-json/wp/v2/users 를 열면 이름과 slug 가 나옵니다. 그리고 그 slug 는 기본 설정에서 로그인 아이디와 같습니다.
아이디가 알려지면 무엇이 달라지나
정확하게 말하는 것이 중요합니다. 사용자 열거는 그 자체로 취약점이 아닙니다. 공격자의 비용을 낮추는 일입니다. 로그인은 아이디와 비밀번호 두 개를 맞혀야 하는 문제인데, 아이디가 공개되면 비밀번호 하나짜리 문제로 줄어듭니다. 여기에 로그인 시도 제한이 없으면 자동화된 대입이 성립합니다.
흘러나가는 경로는 하나가 아닙니다.
닫는 방법 — 두 겹으로
첫 겹은 아이디와 표시 이름을 분리하는 것입니다. 이것이 가장 근본적입니다. 어떤 경로로 이름이 새더라도 그것이 로그인 아이디가 아니면 위의 계산이 성립하지 않습니다.
# slug(=user_nicename) 을 로그인 아이디와 다르게 바꾼다
wp user update 1 --user_nicename=team --display_name="WPER 팀"
둘째 겹은 경로를 닫는 것입니다. 로그아웃 상태에서는 사용자 컬렉션 자체를 없애고, 작성자 아카이브 탐색을 끊습니다. 로그인 상태는 건드리지 않아야 블록 에디터의 작성자 선택이 계속 동작합니다.
// 로그아웃 상태에서만 사용자 라우트를 걷어낸다.
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( is_user_logged_in() ) {
return $endpoints;
}
unset( $endpoints['/wp/v2/users'] );
unset( $endpoints['/wp/v2/users/(?P<id>[d]+)'] );
return $endpoints;
} );
// 작성자 아카이브를 쓰지 않는 사이트라면 탐색 자체를 끊는다.
add_action( 'template_redirect', function () {
if ( is_author() || isset( $_GET['author'] ) ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
} );
두 번째 조각은 작성자 아카이브를 쓰지 않는 사이트에서만 맞습니다. 여러 필자가 있고 작성자 페이지가 콘텐츠 자산인 사이트라면 이 코드는 자산을 지우는 코드가 됩니다. 보안 설정을 복사해 붙이기 전에 그 사이트에서 그 기능이 쓰이는지 먼저 봅니다.
서버 계층에서도 한 겹
애플리케이션에서 막았다면 서버에서도 같은 규칙을 둡니다. nginx 에서 비인증 /wp-json/wp/v2/users 요청과 ?author= 질의를 끊어 두면, 플러그인이 필터를 덮어쓰거나 테마가 바뀌어도 한 겹이 남습니다. 같은 방어를 두 계층에 두는 이유는 중복이 아니라, 한 겹이 빠져도 뚫리지 않게 하기 위해서입니다.
막았는지 확인하는 것도 잊지 마세요. 로그아웃 상태의 브라우저나 curl 로 실제로 두드려 보고, 열려 있는 경로가 남았는지 봅니다. 우리가 만든 무료 진단 도구는 이 항목을 점검 목록에 포함하고 있어서, 사이트를 옮기거나 플러그인을 바꾼 뒤에 다시 열렸는지도 잡아냅니다.
로그인 보호와 하드닝 전반은 보안 아카이브에 정리돼 있습니다.
다음 회차
여기까지가 보안입니다. 다음 회차는 성능 — API 응답을 어느 계층에 캐시할 것인가, 그리고 캐시의 진짜 어려움인 무효화를 어떻게 설계하는가입니다.