Technote

보안 실무

연재 로컬에서 프로덕션까지 8부 중 3부

wp-config 와 비밀값 — 환경마다 다른 값을 코드에 넣지 않기

커밋된 비밀값은 이미 유출된 비밀값입니다. 히스토리를 다시 써도 되돌아오지 않고, 남는 해결은 교체 하나뿐입니다.

wp-config.php 는 워드프레스에서 유일하게 환경마다 내용이 달라야 하는 파일입니다. DB 자격증명 · 인증 salt · 디버그 스위치 · 캐시 설정이 여기 들어 있고, 이 값들은 로컬과 스테이징과 프로덕션에서 전부 다릅니다. 그래서 이 파일은 저장소에 들어갈 수 없습니다 — 하나를 커밋하면 나머지 환경이 틀리게 됩니다.

환경별 값을 두는 두 가지 방법

실무에서 통하는 구성은 둘입니다. 어느 쪽이든 원칙은 같습니다 — 저장소에는 “무엇이 필요한지” 만 있고, “값이 무엇인지” 는 없다.

값이 사는 자리 — 저장소에는 형식만 남고 값은 환경이 갖는다

첫째, 추적되지 않는 파일wp-config.php 가 읽는 방법입니다. 저장소에는 그 파일의 샘플만 커밋합니다. 둘째, 환경변수를 읽는 방법입니다. 컨테이너 환경에서 자연스럽고, PHP-FPM 이라면 풀 설정의 env[] 로 주입합니다.

<?php
// wp-config.php — 저장소에 두지 않는다.

$local = __DIR__ . '/wp-config-local.php';
if ( is_readable( $local ) ) {
    require $local;          // DB_*, salt, 그 밖의 비밀값
}

$env = getenv( 'WP_ENV' ) ?: 'production';

define( 'WP_ENVIRONMENT_TYPE', $env );
define( 'WP_DEBUG',           'production' !== $env );
define( 'WP_DEBUG_DISPLAY',   false );
define( 'DISALLOW_FILE_EDIT', true );

// ⚠ 이 정의는 전부 아래 줄보다 위에 있어야 한다.
require_once ABSPATH . 'wp-settings.php';

마지막 줄의 주석이 실무에서 가장 자주 걸리는 지점입니다. 상수는 wp-settings.php 를 부르기 전에 정의돼야 합니다. 그 아래에 적으면 이미 워드프레스가 부팅을 마친 뒤라 조용히 무시됩니다 — 에러도 경고도 없이 설정만 안 먹습니다.

참고로 WP_ENVIRONMENT_TYPE 은 워드프레스가 직접 읽는 상수입니다. 코드에서는 wp_get_environment_type() 으로 받으며 값은 local · development · staging · production 넷입니다. 5회차의 스테이징 안전장치가 이 값을 근거로 동작합니다.

값을 손으로 넣지 않는다

새 환경을 세울 때 설정 파일을 손으로 편집하면 오타와 누락이 생깁니다. WP-CLI 가 이 작업을 명령으로 처리합니다.

wp config create --dbname=wp --dbuser=wp --dbpass=... --skip-check
wp config set WP_ENVIRONMENT_TYPE staging
wp config set WP_DEBUG true --raw     # 따옴표 없이 넣는다
wp config get DB_NAME
wp config shuffle-salts               # 인증 키 8개를 새로 발급

--raw 가 없으면 true문자열로 들어갑니다. 문자열 "true" 도 참으로 평가되므로 WP_DEBUG 에서는 우연히 동작하지만, 숫자를 기대하는 상수에서는 그렇지 않습니다. 습관을 정확하게 들이는 편이 낫습니다.

커밋된 비밀값은 이미 유출된 비밀값이다

실수는 일어납니다. 중요한 것은 그다음에 무엇을 하는가입니다. 과거 커밋에 비밀값이 있는지 확인하는 방법부터 보겠습니다.

git log -p -S 'DB_PASSWORD' --all -- wp-config.php

여기서 무언가 나왔다면, 그 값은 교체해야 합니다. 히스토리를 다시 쓰는 도구가 있지만 그것은 저장소를 정리할 뿐입니다 — 이미 만들어진 클론, 포크, CI 로그, 백업, 화면 캡처는 되돌아오지 않습니다. 유출 여부를 우리가 통제할 수 없다면 유출됐다고 가정하는 것이 유일하게 안전한 전제입니다.

커밋된 비밀값의 처리 순서 — 두 번째 칸을 건너뛰면 나머지는 위안일 뿐이다

교체 대상은 DB 비밀번호 · API 키 · 웹훅 키, 그리고 인증 salt 입니다. salt 를 바꾸면 모든 세션이 끊겨 전원이 다시 로그인해야 하는데, 그것이 바로 salt 교체의 목적입니다 — 탈취된 쿠키를 무효화하는 것이니까요. 마지막으로 파일 권한도 봅니다. wp-config.php 는 웹서버가 읽을 수만 있으면 되므로 640 이면 충분합니다.

비밀값 처리와 서버 하드닝은 보안 아카이브에서 이어지고, 운영 중인 사이트의 설정 · 권한 · 취약점을 한 번에 점검받고 싶다면 최적화 지원 사업에 진단이 포함돼 있습니다.

다음 회차

코드와 설정이 정리됐으니 남은 것은 데이터입니다. 다음 회차는 이 연재에서 가장 많이 잘못 알려진 주제 — DB 동기화이고, SQL 로 도메인을 치환하면 왜 설정이 통째로 사라지는지를 다룹니다.

이 주제의 다른 글

노하우 목록으로

보안 실무

업로드 디렉터리의 PHP 실행 차단 — 마지막 관문

업로드 취약점은 파일을 올리는 데서 끝나지 않습니다. 올라간 파일이 실행될 때 원격 코드 실행이 됩니다. uploads 에서 PHP 를 못 돌게 하면 그 승격이 끊깁니다.

운영 · 관리자 2분 읽기

보안 입문

DISALLOW_FILE_EDIT 하나로 막히는 공격 경로

관리자 비밀번호가 한 번 새면, 내장 테마 편집기는 웹셸 설치 화면이 됩니다. 상수 한 줄이 그 최단 경로를 지웁니다.

개발자 · 운영 · 관리자 2분 읽기

₩270,000 · 신청하기