MySQL 서버 보안 설정 강화를 위한 모범 사례

최소 권한 사용자, 호스트 제한, TLS, 안전한 my.cnf 설정, 패치 및 기본 감사를 통해 MySQL을 강화합니다.

MySQL 서버 보안 설정 강화를 위한 모범 사례

MySQL 강화는 간단한 질문에서 시작됩니다: 하나의 애플리케이션 비밀번호가 유출되면 공격자가 무엇을 얻을 수 있을까요? 답이 "어디서나 모든 데이터베이스"라면 서버에 더 엄격한 사용자, 네트워크 규칙 및 구성이 필요합니다.

일상적인 관리를 어렵게 만들지 않으면서 침해의 폭발 반경을 줄이기 위해 이러한 방법을 사용하세요.

1. 사용자 및 액세스 관리: 최소 권한 원칙

효과적인 사용자 관리는 MySQL 보안의 기초입니다. 사용자에게 꼭 필요한 권한만 부여하는 것이 가장 중요합니다.

특정 애플리케이션을 위한 특정 사용자 생성

애플리케이션 연결에 root 사용자를 사용하지 마세요. 대신 세분화된 권한을 가진 전용 사용자를 생성하세요.

CREATE USER 'my_app_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT SELECT, INSERT, UPDATE, DELETE ON `your_database`.* TO 'my_app_user'@'localhost';

강력한 비밀번호 적용

강력하고 고유한 비밀번호는 첫 번째 방어선입니다. 가능하면 MySQL의 내장 비밀번호 검증 플러그인을 활용하세요.

  • 복잡성: 대문자, 소문자, 숫자 및 기호를 혼합하세요.
  • 길이: 최소 12-16자를 목표로 하세요.
  • 고유성: 비밀번호를 재사용하지 마세요.
  • 교체: 정기적인 비밀번호 변경 정책을 구현하세요.

validate_password 컴포넌트(MySQL 8.0+) 또는 플러그인(MySQL 5.7+)을 활성화할 수 있습니다:

INSTALL COMPONENT 'file://component_validate_password';
-- 또는 이전 버전의 경우
INSTALL PLUGIN validate_password SONAME 'validate_password.so';

-- 강도 정책 구성 (예: MEDIUM: 8자 이상, 대소문자 혼합, 숫자, 특수문자)
SET GLOBAL validate_password.policy = MEDIUM;
SET GLOBAL validate_password.length = 12;

기본 및 사용하지 않는 사용자 제거

MySQL에는 관리 및 내부 계정이 포함될 수 있습니다. mysql.sessionmysql.sys와 같은 필수 시스템 계정은 그대로 두되, 익명 계정과 더 이상 사용하지 않는 모든 사용자 또는 애플리케이션 계정은 제거하세요.

-- 익명 사용자 확인:
SELECT user, host FROM mysql.user WHERE user = '';
-- 익명 사용자 삭제 (발견된 경우):
DROP USER ''@'localhost';

-- 테스트 데이터베이스 제거 (존재하는 경우):
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%';
FLUSH PRIVILEGES;

호스트 액세스 제한

사용자 계정이 특정 IP 주소 또는 호스트 이름에서만 연결할 수 있도록 제한하세요. 꼭 필요하고 다른 강력한 보안 제어와 함께 사용하지 않는 한 호스트에 와일드카드로 %를 사용하지 마세요.

-- 사용자는 localhost에서만 연결 가능
CREATE USER 'admin'@'localhost' IDENTIFIED BY 'AnotherStrongPass!';

-- 사용자는 특정 IP 주소에서만 연결 가능
CREATE USER 'backup_user'@'192.168.1.100' IDENTIFIED BY 'BackupPass!';

GRANT OPTION 사용 시 주의

WITH GRANT OPTION 절은 사용자가 자신의 권한을 다른 사용자에게 부여할 수 있게 합니다. 이는 신뢰할 수 없는 사용자에게 부여될 경우 상당한 보안 위험이 될 수 있습니다. 이 기능이 실제로 필요한 관리 계정에만 드물게 사용하세요.

-- 권한 부여 기능이 있는 사용자 (매우 주의해서 사용)
CREATE USER 'superadmin'@'localhost' IDENTIFIED BY 'SuperAdminPass!';
GRANT ALL PRIVILEGES ON *.* TO 'superadmin'@'localhost' WITH GRANT OPTION;

2. 네트워크 보안: 데이터베이스 격리

네트워크 수준 제어는 MySQL 서버에 대한 무단 외부 액세스를 방지하는 데 중요합니다.

방화벽 구성

MySQL의 기본 포트(3306)에 대한 연결을 신뢰할 수 있는 IP 주소 또는 네트워크에서만 허용하세요. 이 포트에 대한 다른 모든 인바운드 연결을 차단하세요.

예시 (Linux의 UFW):

sudo ufw enable
sudo ufw allow from 192.168.1.0/24 to any port 3306
sudo ufw deny 3306
sudo ufw status

예시 (firewalld를 사용하는 CentOS/RHEL):

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --permanent --remove-port=3306/tcp --zone=public # 전역적으로 열려 있지 않은지 확인
sudo firewall-cmd --reload

SSL/TLS로 연결 보호

SSL/TLS를 사용하여 클라이언트와 MySQL 서버 간의 모든 트래픽을 암호화하여 도청 및 중간자 공격(MitM)을 방지하세요. 이는 신뢰할 수 없는 네트워크를 통한 연결에 특히 중요합니다.

SSL/TLS를 활성화하려면 일반적으로 SSL 인증서와 키를 생성한 다음 my.cnf를 구성해야 합니다:

# my.cnf
[mysqld]
ssl_ca=/etc/mysql/certs/ca.pem
ssl_cert=/etc/mysql/certs/server-cert.pem
ssl_key=/etc/mysql/certs/server-key.pem

그런 다음 클라이언트는 SSL/TLS를 사용하여 연결하도록 구성해야 하며, 종종 사용자의 GRANT 문에 REQUIRE SSL을 사용합니다:

CREATE USER 'ssl_user'@'%' IDENTIFIED BY 'SSLUserPass!';
GRANT SELECT ON `your_database`.* TO 'ssl_user'@'%' REQUIRE SSL;

특정 IP 주소에 바인딩

기본적으로 MySQL은 사용 가능한 모든 네트워크 인터페이스(0.0.0.0)에서 수신 대기할 수 있습니다. 연결을 수락해야 하는 인터페이스(예: 로컬 애플리케이션의 경우 localhost, 내부 연결의 경우 사설 네트워크 IP)에서만 수신 대기하도록 제한하세요.

# my.cnf
[mysqld]
bind-address = 127.0.0.1  # 로컬 연결 전용
# 또는
bind-address = 192.168.1.10  # 특정 내부 IP용

: 애플리케이션과 MySQL 서버가 동일한 머신에 있는 경우 bind-address = 127.0.0.1(localhost)이 가장 안전한 옵션입니다. 외부 연결을 완전히 차단하기 때문입니다.

3. 구성 파일 강화 (my.cnf / my.ini)

MySQL 구성 파일(Linux의 my.cnf, Windows의 my.ini)은 보안을 강화할 수 있는 다양한 매개변수를 제공합니다.

사용하지 않는 기능 비활성화

배포에 필요하지 않은 기능을 비활성화하여 공격 표면을 최소화하세요.

  • local_infile = 0: LOAD DATA LOCAL INFILE을 비활성화합니다. 이 기능은 클라이언트와 서버 모두 허용할 때 클라이언트 호스트에서 파일을 읽을 수 있습니다.
    [mysqld]
    local_infile = 0
    
  • skip-networking: 데이터베이스가 동일한 서버의 애플리케이션에서만 액세스되는 경우 네트워킹을 완전히 비활성화하세요. 이렇게 하면 모든 연결이 Unix 소켓 또는 명명된 파이프를 사용하도록 강제됩니다.
    [mysqld]
    skip-networking
    
  • symbolic-links = 0: 데이터베이스 테이블스페이스에 대한 심볼릭 링크 사용을 방지합니다. 이는 MySQL 데이터 디렉토리 외부의 파일에 액세스하는 데 악용될 수 있습니다.
    [mysqld]
    symbolic-links = 0
    
  • secure_file_priv: LOAD DATA INFILESELECT ... INTO OUTFILE과 같은 파일 가져오기 및 내보내기 작업에 사용되는 디렉토리를 제한합니다.
    [mysqld]
    secure_file_priv = /var/lib/mysql-files
    

secure_file_priv를 MySQL 서비스 계정이 소유한 전용 디렉토리로 설정하세요. /tmp, 애플리케이션 업로드 디렉토리 또는 신뢰할 수 없는 사용자가 쓸 수 있는 경로를 가리키지 마세요.

옵션 파일에서 일반 텍스트 비밀번호 방지

클라이언트 옵션 파일은 실수로 데이터베이스 비밀번호를 읽을 수 있는 모든 사람에게 노출할 수 있습니다. 자동화를 위해 자격 증명을 저장해야 하는 경우 파일 권한을 제한하고 전용 낮은 권한 계정을 사용하세요.

chmod 600 /home/backup/.my.cnf
chown backup:backup /home/backup/.my.cnf

대화형 관리를 위해서는 비밀번호를 묻는 메시지를 선호하세요:

mysql -u admin -p

4. 서버 패치 및 확인

보안 구성만으로 패치되지 않은 서버를 보호할 수는 없습니다. 일반 패키지 또는 공급업체 업데이트 프로세스를 통해 MySQL, 클라이언트 라이브러리 및 운영 체제를 최신 상태로 유지하세요.

my.cnf를 변경한 후 유지 관리 기간 동안 유효성을 검사하고 다시 시작하세요:

mysqld --validate-config
sudo systemctl restart mysql
sudo systemctl status mysql

패키지 이름과 서비스 이름은 배포판에 따라 다릅니다. 일부 시스템은 mysql 대신 mysqld를 사용합니다.

5. 액세스 모니터링 및 권한 검토

강화는 첫 번째 패스 후에 끝나지 않습니다. 특히 애플리케이션 변경 또는 직원 변동 후에 계정과 권한을 정기적으로 검토하세요.

SELECT user, host, account_locked FROM mysql.user ORDER BY user, host;
SHOW GRANTS FOR 'my_app_user'@'localhost';

% 호스트, 광범위한 *.* 권한, FILE, SUPER, SYSTEM_USER 또는 WITH GRANT OPTION을 사용하는 계정을 주시하세요. 이러한 권한은 소수의 관리 계정에 유효할 수 있지만, 일상적인 애플리케이션 사용자에게는 절대 나타나서는 안 됩니다.

결론

MySQL을 계층적으로 강화하세요. 전용 최소 권한 사용자로 시작하고, 연결할 수 있는 위치를 제한하고, 네트워크를 통과할 때 트래픽을 암호화하고, 사용하지 않는 위험한 기능을 비활성화하고, 서버를 패치된 상태로 유지하세요. 그런 다음 권한 검토를 예약하여 오래된 액세스가 조용히 다음 사고가 되지 않도록 하세요.