
안녕하세요?
오늘은 DMARC, SPF를 사용하여
이메일 발송 관련해서 변조하는 공격 패턴을 방지하는 방법을 알아보도록 하겠습니다.

DNS 기반 이메일 보안 메커니즘을 실무 관점에서 정리한 가이드입니다.
대상 독자: 클라우드 인프라 엔지니어, DevOps, 도메인/메일 운영 담당자
01. 배경
- 이메일은 기본적으로 "신뢰를 전제하지 않는" 프로토콜이다
SMTP(Simple Mail Transfer Protocol)는 1982년에 설계되었습니다.
당시에는 보안보다 연결성이 우선이었기 때문에, 발신자를 검증하는 메커니즘이 프로토콜에 내장되지 않았습니다.
즉, 누구든지 기술적으로는 아래처럼 From: 헤더를 위조할 수 있습니다.
From: no-reply@trusted-bank.com
To: victim@example.com
Subject: 긴급 계정 확인 요청
이 구조적 취약점을 악용한 것이 피싱(Phishing) 과 스푸핑(Spoofing) 입니다.
| 위협 유형 | 설명 | 예시 |
| 도메인 스푸핑 | 신뢰 도메인 위조 | @samsung.com 사칭 발신 |
| 피싱 | 위조 메일로 자격증명 탈취 | 가짜 로그인 링크 유도 |
| BEC (Business Email Compromise) | 임원 사칭 송금 지시 | CEO/CFO 사칭 |
| 이메일 중간자 변조 | 전송 중 내용 수정 | 계좌번호 교체 |
이 네 가지 위협을 각각 대응하기 위해 등장한 것이 SPF, DKIM, DMARC 입니다.
02. TXT 레코드
- TXT 레코드는 DNS의 범용 텍스트 저장소입니다.
- 원래는 사람이 읽는 메모 용도로 설계되었지만, 현재는 다음 용도로 광범위하게 사용됩니다.
| 용도 | 예시 값 |
| SPF 발신 서버 선언 | v=spf1 include:... |
| DKIM 공개키 등록 | v=DKIM1; k=rsa; p=MIGf... |
| 도메인 소유권 인증 | google-site-verification=... |
| DMARC 정책 선언 | v=DMARC1; p=reject; rua=... |
중요한 특성
- 하나의 도메인에 TXT 레코드를 여러 개 등록할 수 있습니다.
- 단, SPF는 도메인당 하나만 유효합니다(여러 개 있으면 PermError 발생).
- TXT 레코드 자체는 단순한 문자열 컨테이너이며, 보안 기능은 그 값을 해석하는 수신 서버 측에서 처리합니다.
2-1. SPF 란?
"우리 도메인을 대신해서 메일을 보낼 수 있는 서버(IP)가 어디인지" DNS에 공개 선언하는 메커니즘
RFC 7208로 표준화되어 있으며, 수신 서버가 발신 IP를 DNS에서 조회해 허가 여부를 판단합니다.
📌 동작 원리
발신 흐름:
성환12 서버 (IP: 203.0.x.x) → From: notice@ssunghwan.tistory.com 로 메일 발송
수신 서버 검증 흐름:
1. From 헤더의 도메인 추출: ssunghwan.tistory.com
2. DNS 조회: ssunghwan.tistory.com TXT 레코드
3. "v=spf1 include:spf1.성환12.com ~all" 발견
4. spf1.성환12.com 내부에 203.0.x.x 포함 여부 확인
5. 포함되면 → SPF PASS
포함 안되면 → SPF SOFTFAIL (~all) 또는 HARDFAIL (-all)
📌 SPF 레코드 문법
v=spf1 [메커니즘...] [all]
📌 주요 메커니즘:
| 메커니즘 | 설명 | 예시 |
| include: | 외부 SPF 레코드 참조 | include:spf1.성환12.com |
| ip4: | 특정 IPv4 허가 | ip4:203.0.113.0/24 |
| ip6: | 특정 IPv6 허가 | ip6:2001:db8::/32 |
| a: | 해당 도메인 A 레코드 IP 허가 | a:mail.example.com |
| mx: | MX 레코드 IP 허가 | mx |
Qualifier(판정):
| 기호 | 의미 | 결과 |
| + (기본값) | Pass | 정상 발신 |
| ~ | SoftFail | 의심, 스팸 마킹 |
| - | Fail | 명시적 거부 |
| ? | Neutral | 판정 보류 |
실제 예시 (all 수식어 비교):
# 느슨한 설정 (초기 단계 권장)
v=spf1 include:spf1.성환12.com ~all
# 엄격한 설정 (정착 후 권장)
v=spf1 include:spf1.성환12.com -all
📌 SPF의 한계
- SPF는 봉투 발신자(Envelope From, Return-Path) 의 IP만 검증합니다.
- 사용자가 메일 클라이언트에서 보는 From: 헤더(Header From)와는 별개입니다.
이 간극을 이용한 공격(Display Name Spoofing)은 SPF만으로 막을 수 없으며,
DMARC의 Alignment 검사 가 이를 보완합니다.
📌 DNS Lookup 제한
SPF 평가 시 include:, a:, mx: 등의 DNS 조회는 최대 10회 로 제한됩니다(RFC 7208 §4.6.4).
이를 초과하면 PermError 처리되어 SPF 검증이 실패합니다.
# 위험 예시 - lookup이 10회를 초과할 수 있음
v=spf1 include:A include:B include:C include:D include:E
include:F include:G include:H include:I include:J ~all
2-2. DKIM 란?
"이 메일이 우리 도메인에서 서명되었고, 전송 중 내용이 변조되지 않았다" 는 것을 공개키 암호화로 증명!
RFC 6376으로 표준화되어 있으며, 비대칭 암호화(공개키/개인키 쌍)를 사용합니다.
📌 동작 원리
발신 측 (성환12 서버):
1. 메일 헤더 + 본문을 해시
2. 성환12가 보유한 개인키(private key)로 서명
3. 서명값을 DKIM-Signature 헤더에 첨부해 발송
DKIM-Signature: v=1; a=rsa-sha256; d=ssunghwan.tistory.com;
s=성환12; h=from:to:subject:date;
bh=base64(body hash); b=base64(signature)
수신 측 (Gmail 등):
1. DKIM-Signature 헤더에서 d=도메인, s=셀렉터 추출
2. DNS 조회: 성환12._domainkey.ssunghwan.tistory.com
3. 공개키(public key) 획득
4. 공개키로 서명 검증
5. 검증 성공 → DKIM PASS / 실패 → DKIM FAIL
📌 DKIM DNS 레코드 구조
레코드 이름 형식: {셀렉터}._domainkey.{도메인}
; 레코드 이름
성환12._domainkey.ssunghwan.tistory.com
; 레코드 값
v=DKIM1; k=rsa; p=ABCDEFGHIJKLMNOP...
| 필드 | 의미 |
| v=DKIM1 | 버전 |
| k=rsa | 키 알고리즘 |
| p=... | Base64 인코딩된 공개키 |
셀렉터(Selector)의 의미
셀렉터는 하나의 도메인이 여러 개의 DKIM 키를 동시에 운영할 수 있게 해주는 식별자입니다.
wiseu._domainkey.purinapetcare.co.kr → mnwise/WiseuCloud용 키
ds._domainkey.purinapetcare.co.kr → DirectSend용 키
google._domainkey.purinapetcare.co.kr → Google Workspace용 키
이 구조 덕분에 서비스별로 DKIM 키를 독립적으로 관리하고 교체할 수 있습니다.
📌 SPF와 DKIM의 결정적 차이
| 구분 | SPF | DKIM |
| 무엇을 검증 | 발신 서버 IP | 메일 헤더 + 본문 무결성 |
| 변조 감지 | 불가능 | 가능 |
| 포워딩 시 | 중계 서버 IP 변경으로 FAIL | 서명은 유지되므로 PASS |
| 메일 본문 보호 | 없음 | 있음 |
2-3. DMARC 란?
"SPF와 DKIM 검증 결과를 종합해, 실패 시 수신 서버가 어떻게 처리할지 정책을 선언하고 리포트를 받는" 메커니즘
RFC 7489로 표준화되어 있으며, SPF와 DKIM 위에 얹히는 정책 레이어 입니다.
DMARC가 추가하는 핵심 개념: Alignment
- SPF와 DKIM은 각각 독립적으로 검증됩니다.
- 그런데 이 둘이 PASS여도 Header From 도메인과 일치하지 않으면 스푸핑일 수 있습니다.
하지만, DMARC의 Alignment 검사가 이를 잡아냅니다.
SPF Alignment: Return-Path 도메인 == Header From 도메인?
DKIM Alignment: DKIM-Signature의 d= 도메인 == Header From 도메인?
둘 중 하나라도 Alignment 통과 + 해당 인증 PASS
→ DMARC PASS
둘 다 실패
→ DMARC FAIL → p= 정책 적용
📌 DMARC 레코드 문법
레코드 이름: _dmarc.{도메인} (항상 이 형식)
_dmarc.ssunghwan.tistory.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@ssunghwan.heo@gmail.com"
주요 태그:
| 태그 | 의미 | 값 예시 |
| v= | 버전 (필수) | DMARC1 |
| p= | 정책 (필수) | none, quarantine, reject |
| sp= | 서브도메인 정책 | none, quarantine, reject |
| rua= | 집계 리포트 수신 주소 | mailto:dmarc@example.com |
| ruf= | 포렌식 리포트 수신 주소 | mailto:forensic@example.com |
| pct= | 정책 적용 비율 (%) | 100 |
| adkim= | DKIM Alignment 강도 | r(relaxed), s(strict) |
| aspf= | SPF Alignment 강도 | r(relaxed), s(strict) |
정책 단계별 의미:
p=none → 아무것도 하지 않음. 리포트만 받음 (모니터링 단계)
p=quarantine → 실패 메일을 스팸함으로 격리
p=reject → 실패 메일을 완전 거부 (가장 강력)
📌 DMARC 도입 권장 단계
1단계: p=none; rua=mailto:dmarc@your-domain.com
→ 2~4주 리포트 수집, 정상 발신 경로 파악
2단계: p=quarantine; pct=10
→ 10%만 격리 적용, 점진적 강화
3단계: p=quarantine; pct=100
→ 전체 격리 적용
4단계: p=reject; pct=100
→ 완전 차단 (최종 목표)
03. 세 가지 레코드의 관계
📌 정리
- SPF : 발신 IP 확인 및 허가 여부 판정
- DKIM : 서명 검증 및 무결성 확인
- DMARC : SPF + DKIM 종합하여 Alignment 검사, 정책에 따라 집행
📌 DMARC 판정 매트릭스
| PASS (Aligned) | PASS (Aligned) | ✅ PASS | 이상적 상태 |
| PASS (Aligned) | FAIL | ✅ PASS | SPF 하나로 충분 |
| FAIL | PASS (Aligned) | ✅ PASS | DKIM 하나로 충분 |
| FAIL | FAIL | ❌ FAIL | p= 정책 실행 |
| PASS (Not Aligned) | FAIL | ❌ FAIL | Alignment 실패 |
각각이 막는 위협
| 위협 | SPF | DKIM | DMARC |
| 외부 IP 스푸핑 | ✅ 차단 | ❌ | ✅ (종합 판정) |
| 메일 내용 변조 | ❌ | ✅ 감지 | ❌ |
| Header From 위조 | ❌ | ❌ | ✅ Alignment |
| 포워딩 메일 오탐 | ❌ (오탐 발생) | ✅ 견고 | 부분적 |
04. 실무 설정 예시
📌 다중 발송 서비스 SPF 통합
여러 이메일 서비스를 쓸 때 SPF는 반드시 하나의 레코드에 통합해야 합니다.
; ❌ 잘못된 설정 - 두 개의 SPF가 있으면 PermError
ssunghwan.tistory.com TXT "v=spf1 include:spf1.성환12.com ~all"
ssunghwan.tistory.com TXT "v=spf1 include:domain.com ~all"
; ✅ 올바른 설정 - 하나의 레코드에 모두 통합
ssunghwan.tistory.com TXT "v=spf1 include:spf1.성환12.com include:domain.com ~all"
📌 서비스별 DKIM 레코드 구성
; mnwise/WiseuCloud용 DKIM
wiseu._domainkey.ssunghwan.tistory.com TXT "v=DKIM1; k=rsa; p=ABCDEFG..."
; DirectSend용 DKIM
ds._domainkey.ssunghwan.tistory.com CNAME dkim.directsend.co.kr
; Google Workspace용 DKIM (설정 시)
google._domainkey.ssunghwan.tistory.com TXT "v=DKIM1; k=rsa; p=GFEDCBA..."
📌 DMARC 설정
; 초기 모니터링 단계
_dmarc.ssunghwan.tistory.com TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@ssunghwan.heo@gmail.com"
; 강화 단계
_dmarc.ssunghwan.tistory.com TXT "v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc-reports@ssunghwan.heo@gmail.com; adkim=r; aspf=r"
05. 추가 권장 항목
SPF/DKIM/DMARC 외에 이메일 및 도메인 보안을 강화하는 추가 메커니즘들입니다.
MTA-STS (Mail Transfer Agent Strict Transport Security)
"우리 메일 서버로의 연결은 반드시 TLS로만 허용한다"
SMTP는 기본적으로 평문 통신이 가능합니다. MTA-STS는 발신 서버에게 TLS 강제를 선언하여 중간자 공격(MITM)을 방어합니다.
; MTA-STS 정책 위치 선언
_mta-sts.ssunghwan.tistory.com TXT "v=STSv1; id=20240101"
; 실제 정책은 HTTPS로 제공
; https://mta-sts.ssunghwan.tistory.com/.well-known/mta-sts.txt
version: STSv1
mode: enforce
mx: mail.ssunghwan.tistory.com
max_age: 86400
TLS-RPT (TLS Reporting)
"TLS 연결 실패를 리포트로 받는다"
MTA-STS와 함께 사용하며, TLS 협상 실패 시 알림을 받을 수 있습니다.
_smtp._tls.ssunghwan.tistory.com TXT "v=TLSRPTv1; rua=mailto:tls-reports@ssunghwan.heo@gmail.com"
BIMI (Brand Indicators for Message Identification)
"DMARC p=reject 도달 후, 수신함에 브랜드 로고를 표시한다"
Gmail, Yahoo 등 주요 메일 클라이언트에서 발신자 아이콘 자리에 공식 로고를 표시합니다. DMARC p=reject 설정이 선행 조건입니다.
default._bimi.ssunghwan.tistory.com TXT "v=BIMI1; l=https://ssunghwan.tistory.com/logo.svg; a=https://ssunghwan.tistory.com/vmc.pem"
- l=: SVG 형식 로고 URL
- a=: VMC(Verified Mark Certificate) - CA로부터 발급받는 브랜드 인증서 (유료)
CAA (Certification Authority Authorization)
"우리 도메인의 SSL/TLS 인증서는 특정 CA만 발급할 수 있다"
이메일과 직접 관련은 없지만, 도메인 보안의 일환으로 도메인 하이재킹 및 인증서 오발급을 방어합니다.
ssunghwan.tistory.com CAA 0 issue "amazon.com"
ssunghwan.tistory.com CAA 0 issue "letsencrypt.org"
ssunghwan.tistory.com CAA 0 issuewild ";" -- 와일드카드 발급 금지
SPF Macro (vali.email 방식)
일부 글로벌 표준에서 사용하는 동적 SPF 평가 방식입니다.
발신 IP, 도메인 등을 변수로 활용해 더 정밀한 허가 목록을 구성할 수 있습니다.
; 매크로 방식 예시
v=SPF1 include:%{i}._ip.%{h}._ehlo.%{D}._spf.vali.email ~All
; %{i} = 발신 IP
; %{h} = EHLO 도메인
; %{D} = 헤더 From 도메인
긴 글 읽어주셔서 감사합니다.
참고 RFC: RFC 7208 (SPF), RFC 6376 (DKIM), RFC 7489 (DMARC), RFC 8461 (MTA-STS), RFC 8460 (TLS-RPT)