목차
[MariaDB] 보안 취약점 점검 #2 보안 관련 플러그인 설치
[MariaDB] 보안 취약점 점검 #3 보안 시나리오
[MariaDB] 보안 취약점 점검 #4 비밀번호 정책 적용
[MariaDB] 보안 취약점 점검 #5 root 계정 보안
[MariaDB] 보안 취약점 점검 #6 사용자 계정 생성
[MariaDB] 보안 취약점 점검 #7 감사 정책 준비
[MariaDB] 보안 취약점 점검 #8 감사 로그 관리
[MariaDB] 보안 취약점 점검 #9 백업 & 소산
인프라 아키텍쳐
테스트를 위한 인프라 구성입니다.
실무에서는 더 복잡한 구성이고 노란 박스안의 개인정보 처리자 영역이 대부분 서비스 서버와 다른 네트워크에 존재 하겠지만 여기서는 IP 대역으로 아래와 같이 구분해서 별도의 네트워크라고 간주하겠습니다.
~192.168.45.99 : IDC
192.168.45.20~192.168.45.29 : Public Network (Web, API, App)
192.168.45.30~192.168.45.39 : Private Network (DB)
192.168.45.100~ : 업무공간
계정별로 접근 가능한 IP 대역을 부여해 보기 위해 위처럼 가상으로 구분하겠습니다.
사용자 계정 생성
사용자 계정을 생성하기 전에 앞서 정리했던 보안 시나리오를 다시 한번 상기해 보겠습니다.
https://opensrc.tistory.com/257
[MariaDB] 보안 취약점 점검 #3 보안 시나리오
이전 페이지에서 필요한 플러그인 설치는 완료 했습니다.https://opensrc.tistory.com/256 [MariaDB] 보안취약점 점검 #2 비밀번호 알고리즘 개선 - 플러그인 설치MariaDB에서 제공하는 주요 비밀번호 보안 관
opensrc.tistory.com
비밀번호
- 최소 숫자 2자리 포함
- 최소 대/소문자 2자리 포함
- 최소 특수문자 2자리 포함
- 비밀번호 최소 길이 10자리 이상
- 유추하기 쉬운 비밀번호 사용 금지
- 사용한 비밀번호 직전 3회 사용 금지
- 비밀번호 유효기간 90일
- 안전한 비밀번호 알고리즘 사용 (md5, sha1 같은 취약한 알고리즘 불가)
계정 및 접근 관리
- 개인별 계정 및 IP 할당
- 개인 계정 별 권한 차등 부여
미리 생성한 webservice database 에 대해
- 관리자 계정 : dba(192.168.45.102)는 모든 권한
- 서비스 계정 : 실제 상용 어플리케이션에서 접속 해서 서비스를 하는 webservice(192.168.45.21, 192.168.45.22) 에게는 읽기 쓰기, 수정, 삭제, 실행 권한
- 매니저 직급 계정 : developer1(195.168.45.102, 매니저) 계정에 읽기, 쓰기, 수정, 삭제, 실행 권한
- 주니어 직급 계정 : developer2(195.168.45.103, 쥬니어) 에게는 읽기, 실행 권한
으로 구분해서 부여해보도록 하겠습니다.
계정 생성 하기전에 이전에 설치한 비밀번호 보안 플러그인이 잘 동작하는지도 확인해 보겠습니다.
password_reuse_check plugin
MariaDB [mysql]> create user 'developer'@'192.168.45.103' identified via ed25519 using password ('보안삭제') password expire interval 90 day;
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements (password_reuse_check)
MariaDB [mysql]>
실제 사용할 계정을 만들었다가 drop 하고 같은 비밀번호로 생성시도 했을때 비밀번호 재사용 정책 위반으로 에러가 발생했습니다
simple_password_check
MariaDB [mysql]> create user 'developer'@'192.168.45.103' identified via ed25519 using password ('Bass') password expire interval 90 day;
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements (simple_password_check)
MariaDB [mysql]>
간단한 문자열 역시 에러가 발생합니다.
cracklib_password_check
MariaDB [mysql]> create user 'developer'@'192.168.45.103' identified via ed25519 using password ('Developer!@QWER1234') password expire interval 90 day;
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements (cracklib_password_check)
MariaDB [mysql]>
예측 가능한 단어로 이뤄진 비밀번호는 복잡해도 사용할 수 없습니다.
모두 정상 작동 하고 있습니다.
사용자 생성
MariaDB [mysql]> select host, user, plugin from user;
+-----------+-------------+-----------------------+
| Host | User | plugin |
+-----------+-------------+-----------------------+
| localhost | mariadb.sys | mysql_native_password |
| localhost | root | ed25519 |
| localhost | mysql | mysql_native_password |
+-----------+-------------+-----------------------+
3 rows in set (0.004 sec)
MariaDB [mysql]> create user 'dba'@'192.168.45.102' identified via ed25519 using password('보안삭제') password expire interval 90 day;
Query OK, 0 rows affected (0.023 sec)
MariaDB [mysql]> create user 'developer1'@'192.168.45.102' identified via ed25519 using password('보안삭제') password expire interval 90 day;
Query OK, 0 rows affected (0.021 sec)
MariaDB [mysql]> create user 'developer2'@'192.168.45.103' identified via ed25519 using password('보안삭제') password expire interval 90 day;
Query OK, 0 rows affected (0.021 sec)
MariaDB [mysql]> create user 'webservice'@'192.168.45.20/255.255.255.252' identified via ed25519 using password('보안삭제') password expire interval 90 day;
Query OK, 0 rows affected (0.045 sec)
MariaDB [mysql]> select user, host, plugin from user;
+-------------+-------------------------------+-----------------------+
| User | Host | plugin |
+-------------+-------------------------------+-----------------------+
| mariadb.sys | localhost | mysql_native_password |
| root | localhost | ed25519 |
| mysql | localhost | mysql_native_password |
| dba | 192.168.45.102 | ed25519 |
| developer1 | 192.168.45.102 | ed25519 |
| webservice | 192.168.45.20/255.255.255.252 | ed25519 |
| developer2 | 192.168.45.103 | ed25519 |
+-------------+-------------------------------+-----------------------+
7 rows in set (0.005 sec)
MariaDB [mysql]>
MySQL 계열 DB에서는 사용자ID가 같아도 Host가 다르면 다른 계정으로 인식한다는 점을 숙지해야 합니다.
Host는 단독 IP로 지정을 할 수 도 있지만, 와일드카드 %를 이용하여 범위를 지정 할 수 있습니다.
developer@% 이면 IP 제한 없이 어디에서든 접속 가능합니다.
developer@10.10.% 이면 10.10.xxx.xxx 네트워크 B클래스 어디서든 접속 가능합니다.
네트워크 마스크 비트를 이용한 developer@192.168.45.0/24 이런식으로 지정은 불가하고
developer@192.168.45.0/255.255.255.0 이런식으로 서브넷 마스크를 이용 할 수 있습니다.
192.168.45.20/255.255.255.252 는 192.168.45.20 ~ 23까지 4개의 IP를 허용 합니다.
권한 부여
dba 계정에 데이터베이스 webservice 의 모든 권한을 부여해보겠습니다.
MariaDB [mysql]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| webservice |
+--------------------+
5 rows in set (0.002 sec)
MariaDB [mysql]> grant all privileges on webservice.* to 'dba'@'192.168.45.102';
Query OK, 0 rows affected (0.004 sec)
MariaDB [mysql]> show grants for 'dba'@'192.168.45.102';
+--------------------------------------------------------------------------------------+
| Grants for dba@192.168.45.102 |
+--------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `dba`@`192.168.45.102` IDENTIFIED VIA ed25519 USING '보안삭제' |
| GRANT ALL PRIVILEGES ON `webservice`.* TO `dba`@`192.168.45.102` |
+--------------------------------------------------------------------------------------+
2 rows in set (0.001 sec)
MariaDB [mysql]> flush privileges;
Query OK, 0 rows affected (0.003 sec)
MariaDB [mysql]>
webservice 계정에 조회, 입력, 수정, 삭제, 실행, 보기 권한을 부여해보겠습니다.
MariaDB [mysql]> grant select, insert, update, delete, execute, show view on webservice.* to 'webservice'@'192.168.45.20/255.255.255.252';
Query OK, 0 rows affected (0.003 sec)
MariaDB [mysql]> show grants for 'webservice'@'192.168.45.20/255.255.255.252';
+----------------------------------------------------------------------------------------------------------------------------+
| Grants for webservice@192.168.45.20/255.255.255.252 |
+----------------------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `webservice`@`192.168.45.20/255.255.255.252` IDENTIFIED VIA ed25519 USING '보안삭제' |
| GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE, SHOW VIEW ON `webservice`.* TO `webservice`@`192.168.45.20/255.255.255.252` |
+----------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.001 sec)
MariaDB [mysql]> flush privileges;
Query OK, 0 rows affected (0.003 sec)
MariaDB [mysql]>
devleper1 계정에 조회, 입력, 수정, 삭제, 실행, 보기 developer2 계정에 조회, 실행, 보기 권한을 부여하겠습니다.
MariaDB [mysql]> grant select, insert, update, delete, execute, show view on webservice.* to 'developer1'@'192.168.45.102';
Query OK, 0 rows affected (0.003 sec)
MariaDB [mysql]> grant select, execute, show view on webservice.* to 'developer2'@'192.168.45.103';
Query OK, 0 rows affected (0.003 sec)
MariaDB [mysql]> show grants for 'developer1'@'192.168.45.102';
+-------------------------------------------------------------------------------------------------------------+
| Grants for developer1@192.168.45.102 |
+-------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `developer1`@`192.168.45.102` IDENTIFIED VIA ed25519 USING '보안삭제' |
| GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE, SHOW VIEW ON `webservice`.* TO `developer1`@`192.168.45.102` |
+-------------------------------------------------------------------------------------------------------------+
2 rows in set (0.001 sec)
MariaDB [mysql]> show grants for 'developer2'@'192.168.45.103';
+---------------------------------------------------------------------------------------------+
| Grants for developer2@192.168.45.103 |
+---------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `developer2`@`192.168.45.103` IDENTIFIED VIA ed25519 USING '보안삭제' |
| GRANT SELECT, EXECUTE, SHOW VIEW ON `webservice`.* TO `developer2`@`192.168.45.103` |
+---------------------------------------------------------------------------------------------+
2 rows in set (0.001 sec)
MariaDB [mysql]>
MariaDB [mysql]> flush privileges;
Query OK, 0 rows affected (0.003 sec)
MariaDB [mysql]>
사용자에게 권한이 없는 명령을 실행하면 아래와 같이 command denied to user ... 에러가 발생 합니다.
참고1 : 권한을 부여 할 때 개별 테이블, 컬럼까지도 상세하게 줄 수 있습니다.
참고2 : 테이블 별로 권한이 복잡해 지는 경우 사용자 관리에 공수가 많이 들 것 같아서 MariaDB에서 role을 생성하고 그 role에 권한을 부여하는 방법을 테스트 해보았지만 원하는 대로 동작하지 않았습니다. https://opensrc.tistory.com/236
잘 아시는 분은 조언 부탁드립니다.
참고3 : DB를 처음 설치 하고 외부에서 DB클라이언트를 통해 접속시 Socket Error (115) 에 나오는 분은 bind_address를 확인해 보세요
외부에서 DBeaver 접속 했을 때 아래와 같은 오류가 발생 했습니다.
Socket fail to connect to 192.168.45.31. Connection refused: getsockopt
Connection refused: getsockopt
다른 ubunut 에서 접속 하면 아래와 같은 에러가 발생 했습니다.
$ mysql -h192.168.45.31 -udeveloper1 -p
Enter password:
ERROR 2002 (HY000): Can't connect to server on '192.168.45.31' (115)
$
외부에서 연결이 안되는 경우는 대부분 방화벽 문제가 많습니다.
$ sudo ufw status
Status: inactive
$ netstat -nltp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.54:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:6011 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp6 0 0 ::1:6010 :::* LISTEN -
tcp6 0 0 ::1:6011 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
$
방화벽은 비활성화 되어 있고 MariaDB가 127.0.0.1:3306 루프백 IP로 바인딩 되어 있네요 이게 문제 인것 같습니다.
오류 메시지로 구글링 하면 대부분 해결책을 찾을 수 있습니다.
두가지를 확인하라고 하네요
SHOW VARIABLES LIKE 'skip_networking'; (result should be off)
SHOW VARIABLES LIKE 'bind_address'; (should not be 127.0.0.1)
MariaDB [(none)]> show variables like 'skip_networking';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| skip_networking | OFF |
+-----------------+-------+
1 row in set (0.005 sec)
MariaDB [(none)]> show variables like 'bind_address';
+---------------+-----------+
| Variable_name | Value |
+---------------+-----------+
| bind_address | 127.0.0.1 |
+---------------+-----------+
1 row in set (0.005 sec)
MariaDB [(none)]>
역시 bind_address 가 문제 였습니다.
my.cnf 에 추가하고 재가동 하면 되는데,
제가 설치한 ubuntu 에는 /etc/mysql/mariadb.conf.d/50-server.cnf 파일에 IP를 변경해 주면 됩니다.
$ sudo vi /etc/mysql/mariadb.conf.d/50-server.cnf
... 생략...
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
bind-address = 192.168.45.31
... 생략
$
bind-address = 0.0.0.0 으로 설정 하면 어디서든 접근 할 수 있습니다.(방화벽, 계정 IP 할당 등 반드시 보안 조치가 필요 할 것 같습니다.)
비밀번호 만료 정보 확인
MariaDB [(none)]> WITH password_expiration_info AS (
-> SELECT User, Host,
-> IF(
-> IFNULL(JSON_EXTRACT(Priv, '$.password_lifetime'), -1) = -1,
-> @@global.default_password_lifetime,
-> JSON_EXTRACT(Priv, '$.password_lifetime')
-> ) AS password_lifetime,
-> JSON_EXTRACT(Priv, '$.password_last_changed') AS password_last_changed
-> FROM mysql.global_priv
-> )
-> SELECT pei.User, pei.Host,
-> pei.password_lifetime,
-> FROM_UNIXTIME(pei.password_last_changed) AS password_last_changed_datetime,
-> FROM_UNIXTIME(
-> pei.password_last_changed +
-> (pei.password_lifetime * 60 * 60 * 24)
-> ) AS password_expiration_datetime
-> FROM password_expiration_info pei
-> WHERE pei.password_lifetime != 0
-> AND pei.password_last_changed IS NOT NULL
-> UNION
-> SELECT pei.User, pei.Host,
-> pei.password_lifetime,
-> FROM_UNIXTIME(pei.password_last_changed) AS password_last_changed_datetime,
-> 0 AS password_expiration_datetime
-> FROM password_expiration_info pei
-> WHERE pei.password_lifetime = 0
-> OR pei.password_last_changed IS NULL;
+-------------+-------------------------------+-------------------+--------------------------------+------------------------------+
| User | Host | password_lifetime | password_last_changed_datetime | password_expiration_datetime |
+-------------+-------------------------------+-------------------+--------------------------------+------------------------------+
| dba | 192.168.45.102 | 90 | 2025-02-27 02:55:47.000000 | 2025-05-28 02:55:47 |
| developer1 | 192.168.45.102 | 90 | 2025-02-28 16:28:30.000000 | 2025-05-29 16:28:30 |
| webservice | 192.168.45.20/255.255.255.252 | 90 | 2025-02-27 02:53:11.000000 | 2025-05-28 02:53:11 |
| developer2 | 192.168.45.103 | 90 | 2025-02-28 16:28:37.000000 | 2025-05-29 16:28:37 |
| mariadb.sys | localhost | 0 | 1970-01-01 09:00:00.000000 | 0 |
| root | localhost | 0 | 2025-03-02 00:34:01.000000 | 0 |
| mysql | localhost | 0 | NULL | 0 |
+-------------+-------------------------------+-------------------+--------------------------------+------------------------------+
7 rows in set (0.009 sec)
MariaDB [(none)]>
잘 적용 되었습니다.
다음 편에서는 감사로그 다루는 방법을 알아 보겠습니다.
끝.
'DataBase' 카테고리의 다른 글
[MariaDB] 보안 취약점 점검 #8 감사 로그 관리 (0) | 2025.03.13 |
---|---|
[MariaDB] 보안 취약점 점검 #7 감사 정책 준비 (0) | 2025.03.01 |
[MariaDB] 보안 취약점 점검 #5 root 계정 보안 (0) | 2025.02.26 |
[MariaDB] 보안 취약점 점검 #4 비밀번호 정책 적용 (0) | 2025.02.25 |
[MariaDB] 보안 취약점 점검 #3 보안 시나리오 (0) | 2025.02.25 |