목차

[MariaDB] 보안 취약점 점검 #1 시작하며

[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로 바인딩 되어 있네요 이게 문제 인것 같습니다.

 

오류 메시지로 구글링 하면 대부분 해결책을 찾을 수 있습니다.

https://stackoverflow.com/questions/64320136/error-2002-hy000-cant-connect-to-mysql-server-on-192-168-1-15-115

 

두가지를 확인하라고 하네요

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)]>

 

잘 적용 되었습니다.

 

다음 편에서는 감사로그 다루는 방법을 알아 보겠습니다.

 

끝.

+ Recent posts