📚 네트워크 · 패킷 분석 › 02. 포트 · 프로토콜 분석 — 82편
이전 글: 81. Web Server Port · 다음 글: 83. DB 포트(3306·1433·5432·1521·6379) 노출 점검
참고: 70. DNS 레코드와 질의 유형 — 메일 서버를 찾는 MX 레코드
침해 사고의 시작점으로 가장 자주 꼽히는 것이 이메일입니다. 피싱 메일, 악성 첨부파일, 발신자 위조 모두 메일 프로토콜 위에서 일어납니다. 또 감염된 내부 PC가 스팸 발송기가 되거나, 탈취된 계정으로 외부에 메일을 대량 발송하는 사고도 있습니다.
관제에서 메일은 "보안 장비(메일 게이트웨이)가 알아서 막는 영역"으로 생각하기 쉽지만, 경보를 해석하려면 다음을 구분할 수 있어야 합니다.
| 프로토콜 | 포트 | 역할 | 암호화 |
|---|---|---|---|
| SMTP | TCP 25 | 메일 서버(MTA) 간 전달 | STARTTLS로 선택적 암호화 |
| SMTP Submission | TCP 587 | 사용자(메일 클라이언트) → 자기 메일 서버로 제출, 인증 필수 | STARTTLS |
| SMTPS | TCP 465 | 제출용, 접속 즉시 TLS | 암묵적 TLS |
| POP3 | TCP 110 | 메일함에서 내려받기(보통 서버에서 삭제) | STARTTLS(STLS) 선택 |
| POP3S | TCP 995 | POP3 over TLS | 암묵적 TLS |
| IMAP | TCP 143 | 메일함을 서버에 둔 채 동기화 | STARTTLS 선택 |
| IMAPS | TCP 993 | IMAP over TLS | 암묵적 TLS |
웹메일 사용자는 브라우저 → 웹메일 서버로 HTTPS(443)만 사용하므로, 사용자 PC에서는 위 포트가 보이지 않을 수 있습니다.
메일은 우편과 비슷하게 봉투와 편지지로 나뉩니다.
| 구분 | 봉투 (SMTP 명령) | 헤더 (메일 본문 안) |
|---|---|---|
| 발신자 | MAIL FROM:<...> | From: |
| 수신자 | RCPT TO:<...> | To:, Cc: |
| 누가 보나 | 메일 서버(전달에 사용) | 사용자(메일 화면에 표시) |
| 위조 | 서버가 검증하지 않으면 가능 | 본문 일부라 쉽게 작성 가능 |
두 값이 달라도 SMTP는 전달합니다. 발신자 위조를 줄이기 위해 SPF(보낼 수 있는 서버 IP 목록), DKIM(서명), DMARC(정책·정렬 검사)를 DNS TXT 레코드로 운영합니다. 수신 서버는 결과를 Authentication-Results 헤더에 기록합니다.
| 프로토콜 | 주요 명령 | 응답 형식 |
|---|---|---|
| SMTP | EHLO, STARTTLS, AUTH, MAIL FROM, RCPT TO, DATA, QUIT | 3자리 코드: 220 준비, 250 성공, 354 본문 입력, 221 종료, 4xx 일시 오류, 5xx 영구 오류 |
| POP3 | USER, PASS, STAT, LIST, RETR, DELE, QUIT | +OK / -ERR |
| IMAP | a1 LOGIN, a2 SELECT INBOX, a3 FETCH, a4 LOGOUT | 태그 + OK / NO / BAD |

그림 1. 메일 한 통이 거치는 구간과 구간별 포트
[보내는 사람 PC]
│ ① SMTP Submission (587, STARTTLS + AUTH)
↓
[보내는 쪽 메일 서버 (MTA)] mail.example.com
│ ② DNS: 받는 도메인의 MX 레코드 질의 → mx.example.net
│ ③ SMTP (25) 서버 간 전달, STARTTLS 가능하면 사용
↓
[받는 쪽 메일 서버 (MTA) + 스팸/보안 필터]
│ SPF·DKIM·DMARC 검사, 첨부 검사 → 메일함 저장
↓
[받는 사람 PC]
④ IMAP(993) 또는 POP3(995)로 메일함 조회
S: 220 mail.example.com ESMTP Postfix
C: EHLO client.example.com
S: 250-mail.example.com
S: 250-STARTTLS
S: 250 ...
C: MAIL FROM:<alice@example.com> ← 봉투 발신자
S: 250 2.1.0 Ok
C: RCPT TO:<bob@example.com> ← 봉투 수신자
S: 250 2.1.5 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: "CEO" <ceo@example.com> ← 헤더 발신자 (봉투와 다를 수 있음)
C: Subject: test
C:
C: body
C: .
S: 250 2.0.0 Ok: queued as 3F2A1C0001 ← 큐 ID (로그 추적 키)
C: QUIT
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu), 인터페이스는 ens33을 예시로 사용합니다. Postfix를 로컬 전용(localhost만 수신)으로 설치해 VM 내부 계정끼리만 메일을 주고받습니다. 외부로 메일을 보내지 않습니다.
# Rocky Linux (기본 설정이 inet_interfaces = localhost)
sudo dnf install -y postfix s-nail nmap-ncat tcpdump
sudo systemctl enable --now postfix
# Ubuntu (설치 중 설정 화면에서 "Local only" 선택)
sudo apt install -y postfix mailutils netcat-openbsd tcpdump
| 항목 | Rocky Linux | Ubuntu |
|---|---|---|
| 메일 로그 | /var/log/maillog | /var/log/mail.log |
| 메일 명령 패키지 | s-nail | mailutils |
| nc 패키지 | nmap-ncat | netcat-openbsd |
# 수신 범위와 리슨 상태 확인
postconf inet_interfaces
sudo ss -tlnp | grep ':25 '
127.0.0.1:25(및 [::1]:25)로만 리슨하는지 확인합니다. 0.0.0.0:25라면 설정을 localhost로 바꾼 뒤 진행합니다.
# 터미널 1: 루프백 25번 캡처
sudo tcpdump -i lo -nn -A 'tcp port 25'
# 터미널 2: -C 옵션으로 줄 끝을 CRLF로 전송
nc -C 127.0.0.1 25
접속 후 아래를 한 줄씩 입력합니다(본인계정은 VM의 실제 사용자 이름).
EHLO lab.local
MAIL FROM:<test@lab.local>
RCPT TO:<본인계정@localhost>
DATA
From: "Notice" <notice@lab.local>
Subject: SMTP lab
hello
.
QUIT
📷 [실습 화면 삽입] nc 화면에서 220 → 250 → 354 →
250 2.0.0 Ok: queued as ...응답 흐름
# 메일함 확인 (s-nail / mailutils 모두 mail 명령 제공)
mail
# 메일 로그
sudo tail -n 20 /var/log/maillog # Rocky
sudo tail -n 20 /var/log/mail.log # Ubuntu
📷 [실습 화면 삽입] 메일 로그에서 같은 큐 ID로 이어지는 smtpd → cleanup → qmgr → local 줄
Postfix 로그 (형식 예시(값은 환경마다 다름))
postfix/smtpd[2201]: connect from localhost[127.0.0.1]
postfix/smtpd[2201]: 3F2A1C0001: client=localhost[127.0.0.1]
postfix/cleanup[2204]: 3F2A1C0001: message-id=<...@lab.local>
postfix/qmgr[1990]: 3F2A1C0001: from=<test@lab.local>, size=412, nrcpt=1 (queue active)
postfix/local[2205]: 3F2A1C0001: to=<user@localhost>, relay=local, delay=12, status=sent (delivered to mailbox)
postfix/qmgr[1990]: 3F2A1C0001: removed
postfix/smtpd[2201]: disconnect from localhost[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5
| 로그 요소 | 의미 |
|---|---|
3F2A1C0001 (큐 ID) | 메일 한 통을 끝까지 추적하는 키 |
client= | 메일을 넘겨준 상대 호스트·IP |
from= | 봉투 발신자 (헤더 From이 아님) |
to=, relay=, status= | 수신자, 전달 방식, 결과(sent / deferred / bounced) |
점검 체크리스트
MAIL FROM)와 헤더 From:을 다르게 넣어도 전달되는 것을 확인했다from=이 봉투 발신자라는 것을 확인했다Received: 줄)에 전달 경로가 기록된 것을 확인했다| 관찰 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
| 내부 PC → 외부 25번 | 거의 없음(메일은 사내 서버 경유) | 여러 외부 IP로 25번 직접 접속 = 스팸 봇 감염 의심 |
Relay access denied 증가 | 설정 오류 클라이언트 몇 건 | 외부에서 제3자 도메인으로 릴레이 시도 반복 (오픈 릴레이 탐색) |
SASL ... authentication failed 반복 | 비밀번호 변경 후 클라이언트 미갱신 | 다수 계정·다수 IP 시도 (계정 대입) |
| 한 계정의 발송량 급증 | 공지 메일 발송 | 계정 탈취 후 스팸·피싱 대량 발송 |
| 봉투 From ≠ 헤더 From | 메일링 리스트, 대행 발송 서비스 | 사내 임원 이름·도메인 사칭 + SPF/DMARC 실패 |
| POP3/IMAP 로그인 해외 IP | 출장 중 사용자 | 짧은 시간 내 서로 먼 국가에서 로그인 |
| 평문 110/143 로그인 | 레거시 클라이언트 | 암호화 포트 정책인데 평문 인증 성공 |
# 형식 예시(값은 환경마다 다름)
postfix/smtpd[3310]: NOQUEUE: reject: RCPT from unknown[203.0.113.45]: 554 5.7.1 <x@example.org>: Relay access denied; from=<a@example.net> to=<x@example.org> proto=ESMTP helo=<pc01>
postfix/smtpd[3322]: warning: unknown[198.51.100.23]: SASL LOGIN authentication failed: authentication failure
LOG=/var/log/maillog # Ubuntu는 /var/log/mail.log
# 릴레이 거부 출발지 집계
sudo grep "Relay access denied" $LOG | grep -oE 'from [^ ]+\[[0-9.]+\]' | sort | uniq -c | sort -rn
# 인증 실패 출발지 집계
sudo grep "authentication failed" $LOG | grep -oE '\[[0-9.]+\]' | sort | uniq -c | sort -rn
# 특정 큐 ID 전체 추적
sudo grep "3F2A1C0001" $LOG
# 봉투 발신자별 발송 건수
sudo grep "qmgr.*from=<" $LOG | grep -oE 'from=<[^>]*>' | sort | uniq -c | sort -rn | head
흔적이 남는 곳
| 위치 | 남는 정보 |
|---|---|
| 메일 게이트웨이/스팸 필터 | 발신 IP, SPF·DKIM·DMARC 결과, 첨부 검사, 차단 사유 |
| MTA 로그 (Postfix 등) | 큐 ID, 봉투 발신·수신자, 릴레이 거부, 인증 실패 |
| 메일 헤더 | Received: 경로, Authentication-Results, Message-ID |
| 방화벽 | 사용자 대역의 외부 25번 발신 시도 |
| POP3/IMAP 서버 로그 | 로그인 성공·실패, 접속 IP (Dovecot 등 서버 종류별 형식 상이) |
관제 시 핵심 포인트
Received: 첫 홉 IP, 인증 결과를 먼저 봅니다.한계와 오탐 주의
Received: 줄은 아래(먼저 거친 곳)에서 위로 쌓이며, 공격자가 가짜 Received: 줄을 미리 넣을 수 있으므로 자기 조직 서버가 기록한 줄부터 신뢰합니다.from=은 봉투 발신자입니다.