본문으로 건너뛰기
서버 & .htaccess

실제로 도움이 되는 WordPress .htaccess 보안 규칙

WordPress .htaccess 보안이라고 팔리는 것들 대부분은 군더더기입니다. 공격 표면을 실제로 바꾸는 규칙은 몇 줄뿐입니다 — 어느 쪽이 어느 쪽인지 정리했습니다

게시됨

“WordPress .htaccess 보안”이라고 팔리는 것들 대부분은 군더더기입니다. 공격 표면을 실제로 바꾸는 규칙은 몇 줄뿐입니다. wp-content/uploads 안에서 PHP 실행을 차단하고, wp-config.php에 대한 직접 접근을 거부하고, 디렉터리 인덱싱을 끄는 것입니다. 흔히 권장되는 것들 — “무차별 대입 공격을 막기 위해” xmlrpc.php 차단하기, WordPress 버전 숨기기, 200줄짜리 악성 봇 user-agent 목록 붙여넣기 — 은 효과가 미미한 것부터 순전히 보여주기식인 것까지 다양합니다. 아래에서 어느 쪽이 어느 쪽인지, 그리고 그 이유를 설명합니다.

먼저 한 가지 짚고 넘어가겠습니다. .htaccess는 Apache(그리고 이 파일을 읽는 LiteSpeed)에서만 동작합니다. nginx에서는 완전히 무시됩니다 — 파일은 그냥 자리만 차지하고 있고 당신은 보호받고 있다고 믿을 뿐입니다. 호스트가 nginx로 돌아간다면 이 내용은 전혀 적용되지 않으며, 대신 server/location 블록이 필요합니다. curl -I https://yoursite.com로 확인해서 Server: 헤더를 먼저 봐야 합니다. 그러지 않으면 서버가 읽지도 않는 파일을 한 시간씩 편집하게 됩니다.

실제로 중요한 규칙: /uploads 안의 PHP 금지

이건 반드시 해야 하는 것입니다. wp-content/uploads는 설계상 누구나 쓸 수 있게 되어 있습니다 — 모든 미디어 업로드, 파일을 저장하는 모든 플러그인이 여기에 씁니다. 공격자가 이 디렉터리에 .php 파일을 넣을 수 있다면(취약한 업로드 핸들러, MIME 타입을 검증하지 않는 이미지 필드, 감염된 플러그인 등을 통해), 단순한 성가심과 완전한 원격 코드 실행(RCE) 침해 사이의 차이는 요청이 들어왔을 때 서버가 그 파일을 실행하느냐에 달려 있습니다. 실행을 거부하면 업로드된 페이로드는 디스크에 놓인 무해한 파일에 불과합니다.

다음 내용을 wp-content/uploads/.htaccess에 넣으세요(파일이 없으면 새로 만드세요):

<FilesMatch "\.php$">
    Require all denied
</FilesMatch>

이것은 Apache 2.4 문법입니다. 구버전인 2.2에서는 Order Deny,Allow / Deny from all이 동일한 역할을 합니다. 한 파일에서 두 문법을 섞어 쓰는 것이 “보안 강화” 후 갑자기 500 Internal Server Error가 나는 가장 흔한 원인입니다 — 저장하는 순간 사이트 전체가 죽는다면 거의 항상 이 때문입니다. apachectl -v로 버전을 확인하세요.

wp-config.php 보호하기

wp-config.php에는 데이터베이스 접속 정보와 인증 솔트가 들어 있습니다. PHP가 실행 중일 때는 이 파일에 대한 직접 요청이 빈 페이지를 반환합니다 — PHP가 파일을 출력하는 대신 실행하기 때문입니다. 위험한 것은 실패 상황입니다. PHP가 죽거나, 마이그레이션 중에 설정이 잘못되거나, 핸들러가 비활성화되면 Apache가 이 파일을 평문으로 서빙해서 DB 비밀번호를 요청하는 누구에게나 그대로 던져줍니다. 접근을 거부하는 것은 나쁜 5분에 대비하는 값싼 보험입니다:

<Files wp-config.php>
    Require all denied
</Files>

디렉터리 인덱싱 끄기

index.php가 없는 폴더에 누군가 방문했을 때 Apache에 Options +Indexes가 켜져 있으면 폴더 내용을 목록으로 보여줍니다 — 모든 백업 파일, 잊고 있던 모든 SQL 덤프까지요. 사이트 전체에서 꺼두세요:

Options -Indexes

심각도는 낮지만 실제로 존재하는 위험이고, 끄는 데 비용도 들지 않습니다.

여기까지가 정직한 핵심 목록입니다. 잘못된 Apache 버전용으로 작성된 포럼 글을 복사해 붙여넣는 대신, WordPress .htaccess 생성기로 이 규칙들을 — 사이트에 500을 내지 않도록 올바른 2.4 대 2.2 문법과 함께 — 조합할 수 있습니다.

하지 말아야 할 것

“무차별 대입 공격 방어를 위해” xmlrpc.php 차단하기. 이것이 다들 반복하는 대표적인 얘기인데, 말한 그대로는 틀렸습니다. 맞습니다, XML-RPC의 system.multicall 메서드는 과거에 공격자가 수많은 로그인 시도를 하나의 요청에 묶을 수 있게 했습니다 — 실제 증폭 효과가 있었죠. 하지만 압도적으로 흔한 무차별 대입 경로는 wp-login.php로 보내는 평범한 POST 요청이고, xmlrpc.php를 차단해도 그것에 대해서는 아무 효과가 없습니다. 무차별 대입은 요청 속도 제한, 강력한 비밀번호, 2단계 인증으로 막는 것이지 엔드포인트 하나를 죽여서 막는 게 아닙니다. XML-RPC를 비활성화할 정당한 이유는 따로 있습니다. 핑백 기능이 DDoS 반사 공격에 악용될 수 있으므로, Jetpack이나 모바일 앱, 핑백을 쓰지 않는다면 이걸 닫아서 공격 표면을 줄이는 것입니다. 다만 이게 당신의 무차별 대입 방어책이라고 스스로 속이지는 마세요. 그건 아니니까요.

WordPress 버전 숨기기 / generator 태그 제거하기. readme.html<meta name="generator"> 태그를 제거하면 보안이 강화된 것 같은 기분이 듭니다. 하지만 공격자는 큐잉된 에셋 쿼리 문자열, 블록 편집기 마크업, 그 밖의 수십 가지 흔적으로 몇 초 만에 버전을 알아냅니다. 당신은 아무것도 숨기고 있지 않으며, 그저 바쁜 척하며 자기만족을 하고 있을 뿐입니다.

거대한 악성 봇 user-agent 및 리퍼러 차단 목록. user-agent는 위조 가능한 HTTP 헤더 하나에 불과합니다. 이런 목록은 붙여넣는 그날 이미 낡았고, 실력 있는 공격자는 아무것도 막지 못하며, Apache는 모든 요청마다 모든 정규식을 평가합니다 — 보안 효과는 0인데 실질적인 성능 세금을 내는 셈입니다. 건너뛰세요.

wp-login.php IP 잠그기. ISP가 당신의 주소를 바꾸기 전까지는 훌륭합니다. 그러다 자기 관리자 화면에서 스스로 잠겨버립니다. 진짜 고정 IP가 있을 때만 실용적입니다.

작성자 열거 리다이렉트(?author=1). 이걸 위해 사람들이 붙여넣는 .htaccess 재작성 규칙은 그것만으로는 불완전합니다 — REST 엔드포인트 /wp-json/wp/v2/users가 여전히 사용자 이름을 나열합니다. 한 경로를 막으면서 다른 경로는 열어두는 것은 보여주기식일 뿐입니다.

여전히 막혔나요?

규칙이 사이트에 500을 낸다면 문법 문제입니다. 마지막에 추가한 블록을 빼고 새로고침하세요 — 그러면 문제가 즉시 분리됩니다. 규칙이 아무 효과가 없어 보인다면, 실제로 Apache에서 돌아가고 있는지, 그리고 해당 디렉터리에 AllowOverride가 활성화되어 있는지 확인하세요(많은 관리형 호스트가 이를 제한합니다). 검증된 템플릿에서 .htaccess 생성기로 파일을 만들고, 중요한 세 가지 규칙만 남기고 나머지는 버리세요.