83. DB 포트(3306·1433·5432·1521·6379) 노출 점검

changseop lee·5일 전

📚 네트워크 · 패킷 분석 › 02. 포트 · 프로토콜 분석 — 83편
이전 글: 82. 메일 프로토콜 SMTP·POP3·IMAP · 다음 글: 84. Linux Service Port
참고(리눅스 시스템 기초): 37. Listening Port 보안 점검 — ss로 LISTEN 포트를 확인하는 기본 절차

1. 왜 알아야 하는가

데이터베이스는 조직이 지켜야 할 데이터가 실제로 저장되는 곳입니다. 그런데 DB 포트는 설정 한 줄로 외부 전체에 열리기 쉬운 포트이기도 합니다.

  • 개발 중 "원격에서 접속이 안 된다"는 이유로 bind 주소를 0.0.0.0으로 바꾸고 방화벽까지 열어 두는 경우
  • 클라우드 보안 그룹에서 DB 포트를 0.0.0.0/0으로 허용한 경우
  • 인증이 기본적으로 약하거나 꺼져 있는 캐시·NoSQL 서비스(예: 설정이 느슨한 Redis)가 외부에 노출된 경우

인터넷에 노출된 DB 포트는 자동화된 스캔과 무차별 대입의 대상이 되며, 인증 없는 서비스라면 데이터 유출이나 서버 장악으로 이어질 수 있다는 점이 널리 알려져 있습니다. 이 글은 "이 포트에서 어떤 DB 서비스가 통신하는가, 그리고 누구에게 열려 있는가"를 확인하는 방법을 정리합니다.


2. 핵심 개념

2-1. 주요 DB 포트

포트서비스프로토콜 특징
3306/TCPMySQL / MariaDB서버가 먼저 인사(greeting) 패킷을 보내며 버전 문자열 포함. TLS는 연결 후 전환 방식
1433/TCPMicrosoft SQL ServerTDS 프로토콜. 이름 있는 인스턴스는 SQL Browser(UDP 1434)로 포트를 알려줌
5432/TCPPostgreSQL클라이언트가 먼저 시작 메시지 전송. SSLRequest로 TLS 전환
1521/TCPOracle DB ListenerTNS 프로토콜. 리스너가 연결을 받아 DB 인스턴스로 전달
6379/TCPRedisRESP라는 단순 텍스트 기반 프로토콜. 기본 평문
(참고) 27017/TCPMongoDB인증 설정이 없던 과거 기본값으로 노출 사고가 잦았던 서비스

포트 번호는 관례일 뿐이며 바꿀 수 있습니다(02편, 20편 참고). 그래서 점검은 포트 번호가 아니라 프로세스에서 출발해야 정확합니다.

2-2. 노출을 결정하는 세 가지 층

DB 포트가 "외부에서 접근 가능하다"는 것은 다음 세 층이 모두 열려 있다는 뜻입니다.

층설정 위치안전한 기본 예
① 바인드 주소DB 설정 파일127.0.0.1 또는 내부 전용 IP
② 네트워크 접근 제어호스트 방화벽, 보안 그룹, 네트워크 방화벽애플리케이션 서버 IP만 허용
③ DB 계정의 허용 호스트DB 내부 계정 테이블, pg_hba.conf 등'app'@'192.168.10.30' 처럼 출발지 한정

한 층만 막혀 있어도 직접 접속은 어렵지만, 한 층에만 의존하면 설정 실수 한 번에 바로 노출됩니다. 그래서 세 층을 모두 점검합니다.

2-3. DB별 바인드·접근 설정 위치

DB바인드 설정접근 제어 설정
MariaDB (Rocky)/etc/my.cnf.d/mariadb-server.cnf의 bind-addressmysql.user 테이블의 host 값
MariaDB (Ubuntu)/etc/mysql/mariadb.conf.d/50-server.cnf의 bind-address (기본 127.0.0.1)동일
PostgreSQLpostgresql.conf의 listen_addresses (기본 localhost)pg_hba.conf
Redisredis.conf의 bind, protected-moderequirepass 또는 ACL
Oraclelistener.ora의 HOST리스너·DB 계정 설정

Rocky의 MariaDB는 설치 버전·설정에 따라 bind-address가 주석 처리되어 모든 인터페이스에서 LISTEN할 수 있으므로, 설정 파일보다 ss 결과로 실제 상태를 확인하는 것이 확실합니다.


3. 동작 원리

애플리케이션 서버가 DB 서버에 접속할 때 거치는 판단 순서입니다.

DB 노출 판단
그림 1. 같은 3306 포트라도 바인딩 주소와 방화벽에 따라 위험도가 다릅니다

 [App 서버 192.168.10.30]                        [DB 서버 192.168.10.40]
          │
          │ TCP SYN → 192.168.10.40:3306
          ↓
  ② 방화벽: 출발지 192.168.10.30 허용? ── 아니오 → 차단 (응답 없음 또는 거부)
          │ 예
          ↓
  ① 소켓: 3306이 192.168.10.40 또는 0.0.0.0에 바인드? ── 아니오 → RST (연결 거부)
          │ 예
          ↓
  TCP 연결 성립 → MariaDB가 greeting(버전 정보) 전송
          ↓
  ③ 계정: 'app'@'192.168.10.30' 존재 + 비밀번호 일치? ── 아니오 → Access denied
          │ 예
          ↓
  쿼리 수행 (TLS 미설정 시 쿼리·결과가 평문으로 전송)

여기서 중요한 관찰 포인트가 있습니다. ①·② 단계에서 막히면 DB 로그에는 아무것도 남지 않고, ③ 단계까지 와야 DB 로그에 인증 실패가 남습니다. 따라서 DB 로그에 외부 IP의 인증 실패가 보인다는 것은 이미 ①·②가 열려 있다는 증거입니다.


4. 실제 명령어 / 실습

실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스 ens33은 예시이며, DB 서버 192.168.10.40, 앱 서버 역할의 두 번째 VM 192.168.10.30도 본인 소유 VM입니다.

4-1. MariaDB 설치

# Rocky Linux
sudo dnf install -y mariadb-server
sudo systemctl enable --now mariadb

# Ubuntu
sudo apt update && sudo apt install -y mariadb-server
sudo systemctl enable --now mariadb

4-2. ① 바인드 주소 확인

sudo ss -tlnp | grep -E ':(3306|1433|5432|1521|6379)\b'

127.0.0.1:3306이면 로컬 전용, 0.0.0.0:3306 또는 *:3306이면 모든 인터페이스입니다. 앱 서버에서 접속이 필요한 경우 내부 IP로만 바인드합니다.

# Rocky: /etc/my.cnf.d/mariadb-server.cnf / Ubuntu: /etc/mysql/mariadb.conf.d/50-server.cnf
# [mysqld] 섹션에 작성
bind-address = 192.168.10.40
sudo systemctl restart mariadb
sudo ss -tlnp | grep ':3306'

4-3. ② 방화벽에서 출발지 제한

# Rocky (firewalld): 앱 서버 IP만 허용
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.30/32" service name="mysql" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rules

# Ubuntu (ufw)
sudo ufw allow from 192.168.10.30 to any port 3306 proto tcp
sudo ufw status numbered

4-4. ③ 계정 허용 호스트 확인

sudo mysql -e "SELECT user, host FROM mysql.user;"

host가 %인 계정은 어디서든 접속 가능한 계정입니다. 실습용 애플리케이션 계정은 출발지를 한정해 만듭니다.

sudo mysql -e "CREATE USER 'app'@'192.168.10.30' IDENTIFIED BY '실습용_비밀번호';"

4-5. 다른 VM에서 도달 여부 확인

# 192.168.10.30(허용)과 그 외 VM(비허용)에서 각각 실행해 비교
timeout 3 bash -c '</dev/tcp/192.168.10.40/3306' && echo "open" || echo "closed/filtered"

📷 [실습 화면 삽입] bind-address 변경 전후 ss -tlnp | grep 3306 결과 비교

📷 [실습 화면 삽입] 허용 VM과 비허용 VM에서의 접속 테스트 결과 비교


5. 결과 확인

ss -tlnp 출력 형식 예시(값은 환경마다 다름)

State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process
LISTEN 0      80      192.168.10.40:3306       0.0.0.0:*     users:(("mariadbd",pid=1532,fd=19))
LISTEN 0      511         127.0.0.1:6379       0.0.0.0:*     users:(("redis-server",pid=988,fd=6))

SELECT user, host 결과 형식 예시(값은 환경마다 다름)

+-------------+---------------+
| User        | Host          |
+-------------+---------------+
| app         | 192.168.10.30 |
| mariadb.sys | localhost     |
| root        | localhost     |
+-------------+---------------+

점검 체크리스트

  • DB 포트가 0.0.0.0/*이 아니라 필요한 주소에만 바인드되어 있는가
  • 호스트 방화벽에서 DB 포트를 특정 출발지로만 허용하는가
  • DB 계정 중 host가 %인 계정이 없는가(있다면 사유가 문서화되어 있는가)
  • 관리자(root) 계정의 원격 접속이 차단되어 있는가
  • Redis 등 인증이 선택 사항인 서비스에 인증이 설정되어 있는가
  • 비허용 VM에서 접속 시도가 실제로 실패하는가

6. 패킷 / 로그 분석

6-1. DB 로그 위치

DB로그 위치(기본값, 설정에 따라 다름)
MariaDB (Rocky)/var/log/mariadb/mariadb.log
MariaDB (Ubuntu)journalctl -u mariadb 또는 설정된 log_error 경로
PostgreSQLRocky: 데이터 디렉터리의 log/, Ubuntu: /var/log/postgresql/
Redisredis.conf의 logfile 설정 경로

MariaDB 인증 실패 형식 예시(값은 환경마다 다름) — 기록 여부는 log_warnings 등 로그 상세도 설정에 따라 다릅니다.

2026-09-26 14:21:07 31 [Warning] Access denied for user 'root'@'198.51.100.23' (using password: YES)

PostgreSQL 인증 실패 형식 예시(값은 환경마다 다름)

2026-09-26 14:22:10.415 KST [2211] FATAL:  password authentication failed for user "postgres"

6-2. 정상일 수 있는 경우 vs 의심해야 하는 경우

관찰 내용정상일 수 있는 경우의심해야 하는 경우
DB 포트로의 연결등록된 앱 서버·백업 서버·DBA 접속 서버등록되지 않은 내부 PC, 외부 IP
인증 실패비밀번호 변경 후 앱 설정 미반영(같은 계정·같은 출발지 반복)root·admin·sa·postgres 등 기본 계정에 대한 다수 출발지 시도
대용량 응답정기 백업, 리포트 배치업무 외 시간 대량 조회 후 외부 전송
Redis 명령캐시 조회·저장(GET/SET)설정 변경 명령(CONFIG SET 등)이 외부·비인가 출발지에서 발생
DB 서버의 아웃바운드복제·백업 대상으로의 연결DB 서버가 인터넷으로 새 연결을 시작

6-3. 확인 명령

# 현재 DB 포트로 연결된 출발지 확인
sudo ss -tn state established '( sport = :3306 )'

# MariaDB 내부에서 접속 세션 확인
sudo mysql -e "SHOW PROCESSLIST;"

# 인증 실패 출발지 집계 (Rocky, 로그 파일 방식일 때)
sudo grep "Access denied" /var/log/mariadb/mariadb.log | grep -oE "@'[^']+'" | sort | uniq -c | sort -rn

7. 보안관제 관점

  • 흔적 위치: 경계 방화벽(외부발 DB 포트 접근 시도·허용 여부), 호스트 방화벽 로그, DB 에러 로그의 인증 실패, DB 감사 로그(설정 시), NetFlow의 DB 서버 대용량 송신.
  • 우선순위 판단: 외부 IP에서 DB 포트로의 연결 성립(허용 로그 + 세션 데이터)이 있다면 단순 스캔보다 훨씬 심각합니다. 차단 로그만 있다면 노출 여부부터 확인합니다.
  • 한계: TLS가 적용된 DB 통신은 쿼리 내용을 볼 수 없고, 평문이라도 네트워크 장비 위치에 따라 DB 트래픽이 수집되지 않을 수 있습니다. DB 자체 감사 로그가 가장 정확한 증거입니다.
  • 오탐 주의: 앱 배포 직후의 인증 실패 폭증은 대부분 설정 문제입니다. 출발지가 등록된 앱 서버인지, 계정이 앱 계정인지 먼저 봅니다.
  • DB 포트 대상 스캔 탐지는 05. 네트워크 스캔 · 공격 징후 분석 시리즈, DB 포트 차단 정책의 방화벽 구현은 06. 방화벽 · IDS/IPS 분석 시리즈에서 다룹니다.

8. 핵심 정리

  • 3306(MySQL/MariaDB), 1433(MSSQL), 5432(PostgreSQL), 1521(Oracle), 6379(Redis)는 원칙적으로 외부 노출 대상이 아닙니다.
  • 노출 여부는 바인드 주소 · 방화벽 · 계정 허용 호스트 세 층을 모두 확인해야 판단할 수 있습니다.
  • 설정 파일보다 ss -tlnp 결과가 실제 상태이며, 포트 번호보다 프로세스 이름에서 출발해야 정확합니다.
  • DB 로그에 외부 IP의 인증 실패가 있다면 네트워크 층이 이미 열려 있다는 뜻입니다.
  • 관제에서는 "등록된 출발지인가", "연결이 실제로 성립했는가", "DB 서버가 대량 송신했는가"를 순서대로 봅니다.

다음 글: 20. 비표준 포트에서 동작하는 서비스 식별

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글