※ MySQL 보안 관련 자료 :
- MySQL에서 보안위해 load_file() 경로 제한하기 (2010.5)
- MySQL 인젝션 공격 방어하는 GreenSQL (2008.11)
출처, 번역 : 해커스랩, http://www.hackerslab.org/korg/view.fhz?menu=news&no=1583
영어 : SecurityFocus, http://www.securityfocus.com/infocus/1726
MySQL 보안 스텝바이스텝 가이드
[출처:시큐리티포커스]MySQL은 인터넷상에서 가장 널리 사용되는 데이터베이스로, 자주 PHP와 연동되어 사용된다. MySQL은 사용하기 편하고, 상대적으로 높은 수행 결과를 가져온다는 이점 말고도 간단하고도 아주 효율적인 보안 메커니즘을 제공한다. 하지만 디폴트로 설치할 경우 루트 패스워드가 없는 등의 위험에 노출될 수 있고, 버퍼 오버플로우 공격에 취약하기 때문에 공격당하기 쉬운 타겟이 되기도 한다.
이 문서는 MySQL 데이터베이스를 로컬 및 리모트 공격으로부터 안전하게 지키는 법을 다룬다.
1.1 기능
이 문서는 다음과 같은 가정 하에 쓰여졌다:
- 아파치 웹 서버와 PHP 모듈이 설치되어 /chroot/httpd 디렉토리에 위치한다.
- MySQL 데이터베이스는 같은 호스트에 설치된 PHP 어플리케이션에 의해서만 사용된다.
- mysqladmin, mysql, mysqldump 같은 디폴트 관리용 툴은 데이터베이스를 관리하는데 사용된다.
- SSH 프로토콜을 통해 리모트 데이터 백업을 수행한다.
1.2 보안 요구사항
보안의 강도를 가장 높게 하려면 MySQL은 다음의 원칙 아래 설치/설정되어야 한다:
- MySQL 데이터베이스는 chroot 환경에서 실행한다.
- MySQL 프로세스는 다른 시스템 프로세스가 사용하지 않는 유일한 UID/GID로 실행한다.
- 로컬 엑세스만 허용한다.
- MySQL 루트 계정은 추정하기 어려운 패스워드를 설정한다.
- 관리자 어카운트명을 변경한다.
- 데이터베이스로의 익명 접속(nobody 계정 사용)을 금지한다.
- 간단한 데이터베이스와 테이블을 모두 삭제한다.
2. MySQL 설치하기
설치는 MySQL 데이터베이스용 운영 시스템에 먼저 사용자 그룹을 만드는 것으로 시작한다:
pw groupadd mysql
pw useradd mysql -c "MySQL Server" -d /dev/null -g mysql -s /sbin/nologin
2.1 MySQL 컴파일 하기
/usr/local/mysql 디렉토리에서 한다.
./configure --prefix=/usr/local/mysql
--with-mysqld-user=mysql
--with-unix-socket-path=/tmp/mysql.sock
--with-mysqld-ldflags=-all-static
make
su
make install
strip /usr/local/mysql/libexec/mysqld
scripts/mysql_install_db
chown -R root /usr/local/mysql
chown -R mysql /usr/local/mysql/var
chgrp -R mysql /usr/local/mysql
일반적으로 서버 설치 프로세스는 MySQL 매뉴얼에 설명된 것과 거의 동일하지만 ./configure에 몇 줄 추가해야 한다. --with-mysqld-ldflags=-all-static 를 사용하는 가장 중요한 이유는 MySQL 서버를 정적으로 링크해 서버를 chroot 하는 프로세스를 간소화 시키기 위함이다. 내용을 추가해 /usr/local/mysql 디렉토리에 프로그램을 설치하고, mysql 어카운트 권한으로 MySQL daemon을 실행하고, /tmp 디렉토리에 mysql.sock socket을 생성하게 한다.
2.2 설정 파일 카피
위의 커맨드를 실행한 후, 디폴트 설정 파일을 데이터베이스 크기(small, medium, large, huge)에 따라 복사한다. 예를 들어:
cp support-files/my-medium.cnf /etc/my.cnf
chown root:sys /etc/my.cnf
chmod 644 /etc/my.cnf
2.3 서버를 시작한다
이제 MySQL을 전부 설치해 실행할 준비가 완료 되었다. MySQL 서버를 다음 커맨드로 실행한다.
/usr/local/mysql/bin/mysqld_safe &
2.4 접속 테스트
데이터베이스에 접속되었는지 다음과 같이 테스트해 본다:
/usr/local/mysql/bin/mysql -u root mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2 to server version: 4.0.13-log
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> show databases;
+----------+
| Database |
+----------+
| mysql |
| test |
+----------+
2 rows in set (0.00 sec)
mysql> quit;
접속이 성공적으로 이루어지면, 데이터베이스를 끈다:
/usr/local/mysql/bin/mysqladmin -u root shutdown
그런 뒤 소프트웨어 보안 설정을 시작하거나, /usr/local/mysql/var/`hostname`.err 로그파일에 저장된 내용을 분석해 문제점의 원인을 제거한다.
3. 서버 chroot 하기
MySQL을 안전하게 하기 위해 제일 먼저 해야 할 것은 chroot 환경이다.
3.1 운영 시스템
이 문서는 FreeBSD 4.7을 기준으로 쓰여졌지만 유닉스 및 유닉스 계열 시스템에도 적용된다.
3.2 chroot 환경 준비하기
다음 디렉토리를 생성한다:
mkdir -p /chroot/mysql/dev
mkdir -p /chroot/mysql/etc
mkdir -p /chroot/mysql/tmp
mkdir -p /chroot/mysql/var/tmp
mkdir -p /chroot/mysql/usr/local/mysql/libexec
mkdir -p /chroot/mysql/usr/local/mysql/share/mysql/english
3.3 엣세스 권한 설정
위에서 생성한 디렉토리 권한을 아래와 같이 설정한다:
chown -R root:sys /chroot/mysql
chmod -R 755 /chroot/mysql
chmod 1777 /chroot/mysql/tmp
3.4 디렉토리 구조 생성
아래 파일을 새로운 디렉토리 구조에 복사한다:
cp /usr/local/mysql/libexec/mysqld /chroot/mysql/usr/local/mysql/libexec/
cp /usr/local/mysql/share/mysql/english/errmsg.sys /chroot/mysql/usr/
local/mysql/share/mysql/english/
cp /etc/hosts /chroot/mysql/etc/
cp /etc/host.conf /chroot/mysql/etc/
cp /etc/resolv.conf /chroot/mysql/etc/
cp /etc/group /chroot/mysql/etc/
cp /etc/master.passwd /chroot/mysql/etc/passwords
cp /etc/my.cnf /chroot/mysql/etc/
3.5 패스워드와 그룹
파일:
/chroot/mysql/etc/passwords 와 /chroot/mysql/etc/group 에서 mysql 어카운트와 그룹을 제외한 모든 라인을 삭제한다. 그런 다음 다음과 같이 패스워드 데이터베이스를 구축한다(FreeBSD에만 해당):
cd /chroot/mysql/etc
pwd_mkdb -d /chroot/mysql/etc passwords
rm -rf /chroot/mysql/etc/master.passwd
3.6 특히 고려할 점
아파치 웹서버의 경우처럼 /dev/null 을 생성하게 한다:
ls -al /dev/null
crw-rw-rw- 1 root sys 2, 2 Jun 21 18:31 /dev/null
mknod /chroot/mysql/dev/null c 2 2
chown root:sys /chroot/mysql/dev/null
chmod 666 /chroot/mysql/dev/null
MySQL 설치시 생성된 테이블을 포함하는 mysql 데이터베이스도 복사한다:
cp -R /usr/local/mysql/var/ /chroot/mysql/usr/local/mysql/var
chown -R mysql:mysql /chroot/mysql/usr/local/mysql/var
3.7 Localization
영어가 아닌 다른 언어를 사용하는 경우, /usr/local/mysql/share/mysql/charsets 디렉토리에서 적당한 charset을 복사한다.
3.8 설정 테스트하기
이제 chroot 환경에서 MySQL을 실행할 준비가 완료 되었다. 아래 커맨드로 올바르게 실행되는지 테스트한다:
chrootuid /chroot/mysql mysql /usr/local/mysql/libexec/mysqld &
에러가 발생하면, ktrace/kdump, strace 같은 커맨드를 사용해 문제점의 원인을 알아내고 제거한다.
아파치나 PHP의 경우 chroot 대신 chrootuid 프로그램으로 mysql 프로세스를 실행했다. chroot와 chrootuid의 주된 차이점은 chrpootuid는 실행되는 프로세스의 소유자를 바꾼다는 데 있다. mysqld는 chroot 환경에서 실행되지만 프로세스의 소유자는 루트가 아니라 mysql 사용자다. chrootuid는 많은 운영 시스템의 경우 디폴트로 설치되지 않기 때문에 다운도르 받아 수작업으로 설치해야 한다. chrootuid 소프트웨어는 ftp://ftp.porcupine.org/pub/security/chrootuid1.3.tar.gz 에서 다운로드 받을 수 있다.
4. 서버 설정하기
다음 할 일은 데이터베이스 서버를 설정하는 것이다. MySQL을 디폴트로 설치했다면, 메인 설정파일은 /etc/my.cnf 겠지만 서버를 chroot 환경에서 실행한다면 /chroot/mysql/etc/my.cnf 와 /etc/my.cnf. 2개의 설정파일을 사용하게 된다. /chroot/mysql/etc/my.cnf 파일은 MySQL 서버에 사용되고, /etc/my.cnf 파일은 MySQL 툴(예를 들어 mysqladmin, mysql, mysqldump 등)에 사용된다. 두 파일 모두 일부 설정을 고쳐줘야 한다.
4.1 리모트 엑세스 차단
먼저 MySQL이 디폴트로 리스닝하는 3306/tcp 포트를 차단해 데이터베이스가 로컬로 설치된 PHP 어플리케이션에 의해서만 사용되게 한다. 3306/tcp 포트를 리스닝하지 못하게 하면 다른 호스트로부터 직접 TCP/IP 접속을 해서 MySQL 데이터베이스를 공격할 가능성이 줄어든다. 그러나 mysql.sock socket 을 통한 로컬 커뮤니케이션은 여전히 가능하다. 3306/tcp 포트를 리스닝하지 못하게 하려면 /chroot/mysql/etc/my.cnf의 [mysqld] 부분에 다음을 추가한다:
리모트로 데이터 백업을 한다든지 등의 이유로 데이터베이스를 리모트 엑세스 해야만 하는 경우 아래와 같이 SSH 프로토콜을 사용한다:
backuphost$ ssh mysqlserver /usr/local/mysql/bin/mysqldump -A > backup
4.2 로컬 보안 개선
다음 해야 할 것은 LOAD DATA LOCAL INFILE 커맨드를 사용치 못하게 해 허가받지 않고 로컬 파일을 읽는 것을 막는다. LOAD DATA LOCAL INFILE 커맨드는 특히 PHP 어플리케이션에서 새로운 SQL Injection 취약점 발견시 문제가 된다.
/chroot/mysql/etc/my.cnf 의 [mysqld] 난에 다음을 추가한다:
set-variable=local-infile=0
추가로 데이터베이스 관리툴 사용을 편하게 하려면 /etc/my.cnf [client] 부분에 다음을 추가한다:
socket = /chroot/mysql/tmp/mysql.sock
이렇게 하면 매번 툴을 실행할 때마다 mysql, mysqladmin, mysqldump 등등의 커맨드에 --socket=/chroot/mysql/tmp/mysql.sock 옵션을 입력하는 번거로움을 덜 수 있다.
4.3 관리자 패스워드 변경
MySQL 보안에 가장 중요한 것은 디폴트 설치시 설정되지 않은 채 비어있는 데이터베이스 관리자 패스워드를 변경하는 것이다. 패스워드를 변경하려면 먼저 MySQL을 실행한다.
chrootuid /chroot/mysql mysql /usr/local/mysql/libexec/mysqld &
그런 뒤 아래와 같이 관리자 패스워드를 변경한다:
/usr/local/mysql/bin/mysql -u root
mysql> SET PASSWORD FOR root@localhost=PASSWORD('새로운 패스워드');
"mysqladmin password" 처럼 커맨드 라인에서 패스워드를 변경하는 습관을 들이는 것은 중요하다. 다른 사용자들이 서버에서 작업할 때 "ps aux" 커맨드를 사용하거나 history 파일 (~/.history, ~/.bash_history etc)을 보면 패스워드가 쉽게 노출될 수 있기 때문이다.
4.4 디폴트 사용자/데이터베이스 삭제하기
그런 뒤 예제 데이터베이스(테스트) 및 루트 어카운트를 제외한 모든 어카운트를 삭제한다:
mysql> drop database test;
mysql> use mysql;
mysql> delete from db;
mysql> delete from user where not (host="localhost" and user="root");
mysql> flush privileges;
이렇게 하면 익명 접속으로 데이터베이스를 설정하는 것을 막을 수 있다.
4.5 관리자 어카운트 변경
디폴트로 설정되는 관리자 어카운트(root)를 무차별 대입 공격이나 딕셔너리 공격 등으로 추측해 내기 어려운 이름으로 변경한다. 변경해 두면 설사 공격을 당하더라도 공격자는 패스워드뿐 아니라 어카운트도 추측해 내야 하기 때문에 공격은 더 어려워 진다.
mysql> update user set user="mydbadmin" where user="root";
mysql> flush privileges;
4.6 history 삭제
실행되는 모든 SQL 커맨드가 저장되는MySQL history 파일(~/.mysql_history)도 삭제해야 한다.
(특히 플레인 텍스트로 저장되는 패스워드)
cat /dev/null > ~/.mysql_history
5. PHP 와 MySQL의 연동
PHP는 /tmp/mysql.sock socket를 사용해 PHP와 연동하기 때문에 PHP를 chroot 환경에 위치시키면 PHP와 SQL이 서로 연동할 수 없게 된다.
이 문제점을 해결하려면 매번 MySQL을 실행할 때마다 PHP chroot 환경에 하드 링크를 생성해 줘야 한다.
ln /chroot/mysql/tmp/mysql.sock /chroot/httpd/tmp/
/chroot/mysql/tmp/mysql.sock socket 과 /chroot/httpd/tmp 디렉토리는 물리적으로 같은 파일 시스템에 위치한다. 만일 그렇지 않다면 이 프로그램들은 서로 연동할 수 없을 것이다. 하드링크는 파일 시스템 사이에서는 적용되지 않는다.
6. 마지막 스텝
특정 PHP 어플리케이션이 사용하는 데이터베이스와 어카운트를 생성한다. 이 어카운트는 PHP 어플리케이션이 사용하는 데이터베이스에만 엑세스가 가능하도록 설정되고, mysql 데이터베이스에는 어떤 시스템이나 관리자 권한(FILE, GRANT, ALTER, SHOW DATABASE, RELOAD, SHUTDOWN, PROCESS, SUPER 등등.)으로도 엑세스할 수 있는 권한이 주어져서는 안된다.
마지막으로 운영 시스템 부팅시 MySQL을 실행하는 쉘 스크립트를 생성한다. 예제 스크립트는 아래와 같다. http://www.securityfocus.com/unix/images/mysql.sh 에서 다운로드 받을 수 있다:
#!/bin/sh
CHROOT_MYSQL=/chroot/mysql
CHROOT_PHP=/chroot/httpd
SOCKET=/tmp/mysql.sock
MYSQLD=/usr/local/mysql/libexec/mysqld
PIDFILE=/usr/local/mysql/var/`hostname`.pid
CHROOTUID=/usr/local/sbin/chrootuid
echo -n " mysql"
case "$1" in
start)
rm -rf ${CHROOT_PHP}/${SOCKET}
nohup ${CHROOTUID} ${CHROOT_MYSQL} mysql ${MYSQLD} >/dev/null 2>&1 &
sleep 5 && ln ${CHROOT_MYSQL}/${SOCKET} ${CHROOT_PHP}/${SOCKET}
;;
stop)
kill `cat ${CHROOT_MYSQL}/${PIDFILE}`
rm -rf ${CHROOT_MYSQL}/${SOCKET}
;;
*)
echo ""
echo "Usage: `basename $0` {start|stop}" >&2
exit 64
;;
esac
exit 0
FreeBSD 시스템의 경우, 위의 스크립트는 /usr/local/etc/rc.d 디렉토리에 mysql.sh 라는 이름으로 저장되어야 한다.
6.1 요약
위의 방법들을 적용시키면 MySQL 보안은 강화될 것이다. 데이터베이스를 chroot 환경에서 실행하고, 3306/tcp 포트로 리스닝하지 못하도록 하고, 추측하기 어려운 패스워드를 사용하면 디폴트로 설치했을 때 당할 수 있는 공격을 면할 수 있다. 100% 안전한 방법은 없다. 그러나 위의 방법들을 적용시키면 최소한 불량한 뜻을 품고 웹서버를 방문하는 사용자들로부터의 공격 가능성을 제한할 수는 있을 것이다.
시큐리티포커스