아웃룩 SMTP 헤더 보는 방법 완벽 가이드: 이메일 포렌식의 핵심
- IT/네트워크(Network)
- 2026. 3. 15.
"스팸 메일은 어디서 왔을까요? 피싱 메일의 진짜 발신자는 누구일까요? 아웃룩 SMTP 헤더를 분석하면 이메일의 모든 비밀을 파악할 수 있습니다. 전 세계 이메일 보안 전문가들이 사용하는 이 기법을 마스터하여 이메일 보안의 달인이 되어보세요."
🎯 목차
- SMTP 헤더란 무엇인가?
- 아웃룩 버전별 헤더 보기 방법
- SMTP 헤더 구성 요소 분석
- 헤더 분석을 통한 보안 진단
- 스팸/피싱 메일 탐지 기법
- 이메일 라우팅 경로 추적
- 실무 활용 사례와 도구
📡 SMTP 헤더란 무엇인가?
🔍 SMTP 헤더의 기본 개념
SMTP 헤더는 이메일이 전송되는 과정에서 각 메일 서버가 추가하는 메타데이터입니다. 마치 택배 상자에 붙는 배송 라벨처럼, 이메일이 어디서 와서 어디로 가는지, 언제 처리되었는지 등의 모든 정보를 담고 있습니다.
🎪 왜 SMTP 헤더 분석이 중요한가?
현실적인 이메일 보안 상황들:
보안 위협 겉보기 정보 헤더 분석 결과
| 스팸 메일 | 발신자: support@apple.com | 실제 발신: spam-server.cn |
| 피싱 메일 | 발신자: security@paypal.com | 실제 발신: fake-paypal.ru |
| 사칭 메일 | 발신자: ceo@company.com | 실제 발신: attacker@gmail.com |
| 악성코드 | 발신자: noreply@microsoft.com | 실제 발신: malware-c2.tk |
💡 놀라운 사실: 전체 이메일의 약 85%가 스팸이나 악성 메일이며, 이들 대부분은 발신자 정보를 위조합니다. SMTP 헤더 분석만으로도 95% 이상의 가짜 메일을 식별할 수 있습니다.
📊 SMTP 헤더 구조 개요
이메일 전송 과정:
발신자 PC → 발신자 메일서버 → 중간 릴레이 서버(들) → 수신자 메일서버 → 수신자 PC
↓ ↓ ↓ ↓ ↓
작성 완료 헤더 추가 헤더 추가 헤더 추가 최종 수신
최종 헤더 구조:
┌─────────────────────────────────────────┐
│ 수신자 서버 헤더 (가장 위) │
├─────────────────────────────────────────┤
│ 중간 서버 헤더 │
├─────────────────────────────────────────┤
│ 발신자 서버 헤더 │
├─────────────────────────────────────────┤
│ 원본 메시지 헤더 (가장 아래) │
└─────────────────────────────────────────┘
🖥️ 아웃룩 버전별 헤더 보기 방법
🏢 Microsoft Outlook (데스크톱 버전)
Outlook 2019/2021/365 (최신 버전)
방법 1: 속성을 통한 접근
1. 분석할 이메일을 더블클릭하여 별도 창에서 열기
2. 상단 리본 메뉴에서 "파일" 탭 클릭
3. "속성" 버튼 클릭
4. "인터넷 헤더" 섹션에서 전체 헤더 확인
방법 2: 우클릭 메뉴 활용
1. 이메일 목록에서 원하는 메일 우클릭
2. "속성" 선택
3. "세부 정보" 탭 클릭
4. "인터넷 헤더" 영역에서 헤더 정보 확인
방법 3: 개발자 도구 활용
1. 파일 → 옵션 → 사용자 지정 리본
2. "개발 도구" 체크박스 선택
3. 이메일 선택 후 개발 도구 탭 → "헤더 정보"
Outlook 2016/2013 (구 버전)
1. 분석할 이메일 더블클릭
2. 메시지 탭 → 태그 그룹의 오른쪽 아래 화살표 클릭
3. "속성" 대화상자에서 "인터넷 헤더" 확인
단축키로 빠른 접근
Alt + Enter : 이메일 속성 대화상자 열기 (헤더 포함)
Ctrl + Alt + P : 속성 대화상자 직접 열기
🌐 Outlook Web App (웹 버전)
outlook.com / office.com 웹버전
방법 1: 메시지 옵션 활용
1. 이메일 열기
2. 오른쪽 상단 "..." (더보기) 메뉴 클릭
3. "메시지 소스 보기" 선택
4. 새 창에서 전체 헤더와 원본 소스 확인
방법 2: 개발자 도구 활용
1. 이메일 열기
2. 브라우저에서 F12 (개발자 도구)
3. Network 탭에서 메일 요청 찾기
4. Response Headers에서 추가 정보 확인
📱 모바일 아웃룩
Outlook 모바일 앱
안드로이드:
1. 이메일 열기 → 오른쪽 상단 "..." → "메시지 세부 정보"
iOS:
1. 이메일 열기 → "세부 정보" → "기술적 세부 정보"
🔬 SMTP 헤더 구성 요소 분석
📋 핵심 헤더 필드 해석
기본 식별 정보
From: user@example.com
To: recipient@domain.com
Subject: 메일 제목
Date: Mon, 15 Jan 2024 14:30:00 +0900
Message-ID: <20240115143000.ABC123@mail.example.com>
필드 설명 보안 분석 포인트
| From | 표시되는 발신자 | 쉽게 위조 가능 ⚠️ |
| Reply-To | 답장 받을 주소 | From과 다르면 의심 🚨 |
| Return-Path | 실제 발신 주소 | 진짜 발신자 확인 ✅ |
| Message-ID | 고유 식별자 | 위조 시 패턴 이상 |
전송 경로 정보 (가장 중요!)
Received: from mail.company.com (mail.company.com [203.0.113.10])
by mx1.receiver.com (Postfix) with ESMTP id 5B2C31040B2A
for <user@receiver.com>; Mon, 15 Jan 2024 14:30:15 +0900 (KST)
Received: from webmail.company.com (unknown [192.168.1.100])
by mail.company.com (Postfix) with ESMTP id 3A1B20F8DA
for <user@receiver.com>; Mon, 15 Jan 2024 14:30:10 +0900 (KST)
Received 헤더 분석 방법:
구문: from [발신서버] ([실제IP]) by [수신서버] with [프로토콜] id [거래ID] for [수신자]; [시간]
분석 포인트:
✅ 서버 이름과 IP 일치 여부
✅ 시간 순서의 일관성 (아래→위로 시간 증가)
✅ 프로토콜 적절성 (ESMTP, SMTP)
⚠️ unknown 또는 [IP주소]로 표시되는 서버
인증 관련 헤더
Authentication-Results: receiver.com;
spf=pass smtp.mailfrom=company.com;
dkim=pass (1024-bit key) header.d=company.com;
dmarc=pass (p=quarantine sp=quarantine pct=100)
Received-SPF: pass (receiver.com: domain of sender@company.com
designates 203.0.113.10 as permitted sender)
client-ip=203.0.113.10;
🔒 보안 검증 헤더
SPF (Sender Policy Framework)
Received-SPF: pass | fail | softfail | neutral | none | temperror | permerror
결과 의미 보안 수준
| pass | 인증된 발신자 | 🟢 안전 |
| fail | 인증되지 않은 발신자 | 🔴 위험 |
| softfail | 의심스러운 발신자 | 🟡 주의 |
| none | SPF 설정 없음 | ⚪ 불명 |
DKIM (DomainKeys Identified Mail)
DKIM-Signature: v=1; a=rsa-sha256; d=company.com; s=selector1;
c=relaxed/relaxed; q=dns/txt;
h=from:to:subject:date:message-id;
bh=base64encodedBodyHash;
b=base64encodedSignature
DMARC (Domain-based Message Authentication)
Authentication-Results: receiver.com;
dmarc=pass action=none header.from=company.com;
📍 지역 및 라우팅 정보
X-Originating-IP와 지역 추적
X-Originating-IP: [203.0.113.50]
X-Forwarded-For: 10.1.1.100, 203.0.113.50
X-Remote-IP: 203.0.113.50
IP 주소 분석 도구:
# Linux/Mac 터미널에서 IP 정보 확인
whois 203.0.113.50
geoiplookup 203.0.113.50
# 온라인 도구
# <https://www.whatismyipaddress.com/ip-lookup>
# <https://www.iplocation.net/>
🕵️ 헤더 분석을 통한 보안 진단
🚨 의심스러운 헤더 패턴
발신자 위조 탐지
정상적인 헤더:
From: support@apple.com
Return-Path: <support@apple.com>
Received: from mail.apple.com (mail.apple.com [17.172.224.47])
Authentication-Results: spf=pass dkim=pass dmarc=pass
의심스러운 헤더:
From: support@apple.com
Return-Path: <noreply@suspicious-domain.tk>
Received: from unknown ([123.456.789.10])
Authentication-Results: spf=fail dkim=none dmarc=fail
스팸 발신자 식별 패턴
⚠️ 의심 패턴들:
- Received: from unknown 또는 localhost
- X-Mailer: bulk mail software 또는 알려진 스팸 도구
- Message-ID가 없거나 이상한 형식
- 여러 개의 X-Forwarded-For 헤더
- 시간대가 일관성 없게 변경됨
🔍 실제 분석 사례
사례 1: 피싱 메일 분석
From: security-team@paypal.com
Subject: 긴급: 계정 보안 확인 필요
Received: from mail-server.suspicious-domain.ru ([185.220.100.240])
by mx.gmail.com with ESMTP id abc123;
Return-Path: <noreply@suspicious-domain.ru>
Authentication-Results: gmail.com;
spf=fail (google.com: domain of noreply@suspicious-domain.ru
does not designate 185.220.100.240 as permitted sender);
dkim=none;
dmarc=fail
분석 결과:
- ❌ From 주소와 Return-Path 불일치
- ❌ 러시아 IP에서 PayPal 사칭
- ❌ SPF, DKIM, DMARC 모두 실패
- 🚨 확정 피싱 메일
사례 2: 정상 메일 분석
From: no-reply@github.com
Subject: [GitHub] Password changed for your account
Received: from github-smtp-2a-02.iad.github.net ([192.30.252.196])
by mx.google.com with ESMTPS id xyz789;
Return-Path: <no-reply@github.com>
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of no-reply@github.com
designates 192.30.252.196 as permitted sender);
dkim=pass header.d=github.com;
dmarc=pass
분석 결과:
- ✅ From과 Return-Path 일치
- ✅ GitHub 공식 서버에서 발송
- ✅ SPF, DKIM, DMARC 모두 통과
- 🟢 정상 메일
🎯 스팸/피싱 메일 탐지 기법
🔍 자동화된 헤더 분석 도구
PowerShell 스크립트로 헤더 분석
# email-header-analyzer.ps1
function Analyze-EmailHeader {
param(
[Parameter(Mandatory=$true)]
[string]$HeaderText
)
Write-Host "=== 이메일 헤더 보안 분석 ===" -ForegroundColor Cyan
# SPF 검사
if ($HeaderText -match "spf=(\\w+)") {
$spfResult = $matches[1]
switch ($spfResult) {
"pass" { Write-Host "✅ SPF: 통과" -ForegroundColor Green }
"fail" { Write-Host "❌ SPF: 실패 (위조 가능성 높음)" -ForegroundColor Red }
"softfail" { Write-Host "⚠️ SPF: 소프트 실패 (의심스러움)" -ForegroundColor Yellow }
default { Write-Host "⚪ SPF: $spfResult" -ForegroundColor Gray }
}
}
# DKIM 검사
if ($HeaderText -match "dkim=(\\w+)") {
$dkimResult = $matches[1]
if ($dkimResult -eq "pass") {
Write-Host "✅ DKIM: 통과 (서명 검증됨)" -ForegroundColor Green
} else {
Write-Host "❌ DKIM: $dkimResult (서명 없음/실패)" -ForegroundColor Red
}
}
# DMARC 검사
if ($HeaderText -match "dmarc=(\\w+)") {
$dmarcResult = $matches[1]
if ($dmarcResult -eq "pass") {
Write-Host "✅ DMARC: 통과" -ForegroundColor Green
} else {
Write-Host "❌ DMARC: $dmarcResult (정책 위반)" -ForegroundColor Red
}
}
# 발신 IP 분석
if ($HeaderText -match "Received: from .* \\[(\\d+\\.\\d+\\.\\d+\\.\\d+)\\]") {
$originIP = $matches[1]
Write-Host "🌐 발신 IP: $originIP" -ForegroundColor Blue
# 사설 IP 대역 확인
if ($originIP -match "^(10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.|192\\.168\\.)" -or $originIP -eq "127.0.0.1") {
Write-Host "⚠️ 사설 IP 대역에서 발신 (의심스러움)" -ForegroundColor Yellow
}
}
# Return-Path와 From 일치성 확인
$fromMatch = [regex]::Match($HeaderText, "From:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})")
$returnPathMatch = [regex]::Match($HeaderText, "Return-Path:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})")
if ($fromMatch.Success -and $returnPathMatch.Success) {
$fromEmail = $fromMatch.Groups[1].Value
$returnEmail = $returnPathMatch.Groups[1].Value
if ($fromEmail -eq $returnEmail) {
Write-Host "✅ From과 Return-Path 일치" -ForegroundColor Green
} else {
Write-Host "❌ From($fromEmail)과 Return-Path($returnEmail) 불일치" -ForegroundColor Red
}
}
}
# 사용 예시
$headers = Get-Clipboard # 클립보드에서 헤더 가져오기
Analyze-EmailHeader -HeaderText $headers
📊 위험도 점수 시스템
헤더 기반 위험도 계산
# email_risk_calculator.py
def calculate_risk_score(headers):
"""이메일 헤더 기반 위험도 점수 계산 (0-100)"""
risk_score = 0
risk_factors = []
# SPF 검사 (가중치: 30점)
if 'spf=fail' in headers:
risk_score += 30
risk_factors.append("SPF 인증 실패")
elif 'spf=softfail' in headers:
risk_score += 15
risk_factors.append("SPF 소프트 실패")
# DKIM 검사 (가중치: 25점)
if 'dkim=fail' in headers or 'dkim=none' in headers:
risk_score += 25
risk_factors.append("DKIM 서명 없음/실패")
# DMARC 검사 (가중치: 20점)
if 'dmarc=fail' in headers:
risk_score += 20
risk_factors.append("DMARC 정책 위반")
# Return-Path와 From 불일치 (가중치: 15점)
import re
from_match = re.search(r'From:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+)', headers)
return_match = re.search(r'Return-Path:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+)', headers)
if from_match and return_match:
if from_match.group(1) != return_match.group(1):
risk_score += 15
risk_factors.append("발신자 주소 불일치")
# 알려진 스팸 서버 (가중치: 10점)
spam_indicators = ['unknown', 'localhost', 'dynamic', 'dial-up']
for indicator in spam_indicators:
if indicator in headers.lower():
risk_score += 10
risk_factors.append(f"의심스러운 서버: {indicator}")
break
return min(risk_score, 100), risk_factors
# 위험도 평가 함수
def assess_risk_level(score):
if score >= 80:
return "🔴 매우 위험 (차단 권장)"
elif score >= 60:
return "🟠 위험 (주의 필요)"
elif score >= 40:
return "🟡 의심스러움 (검토 필요)"
elif score >= 20:
return "🟢 낮은 위험"
else:
return "✅ 안전"
# 사용 예시
headers = """
From: security@paypal.com
Return-Path: <noreply@suspicious-domain.tk>
Authentication-Results: spf=fail dkim=none dmarc=fail
Received: from unknown ([123.45.67.89])
"""
score, factors = calculate_risk_score(headers)
risk_level = assess_risk_level(score)
print(f"위험도 점수: {score}/100")
print(f"위험 수준: {risk_level}")
print(f"위험 요소: {', '.join(factors)}")
🗺️ 이메일 라우팅 경로 추적
🛤️ Received 헤더로 경로 분석
이메일 여행 경로 시각화
# email_route_tracer.py
import re
from datetime import datetime
def parse_received_headers(email_headers):
"""Received 헤더들을 파싱하여 라우팅 경로 추출"""
received_pattern = r'Received:\\s*from\\s+([^\\s]+).*?\\[([^\\]]+)\\].*?by\\s+([^\\s]+).*?;\\s*(.+?)(?=\\n[^ \\t]|\\n$)'
received_entries = []
matches = re.finditer(received_pattern, email_headers, re.DOTALL | re.IGNORECASE)
for match in matches:
from_server = match.group(1)
from_ip = match.group(2)
to_server = match.group(3)
timestamp_str = match.group(4).strip()
# 시간 파싱 시도
try:
# 다양한 시간 형식 처리
timestamp_str = re.sub(r'\\([^)]+\\)$', '', timestamp_str).strip()
timestamp = datetime.strptime(timestamp_str, '%a, %d %b %Y %H:%M:%S %z')
except:
timestamp = None
received_entries.append({
'from_server': from_server,
'from_ip': from_ip,
'to_server': to_server,
'timestamp': timestamp,
'raw_timestamp': timestamp_str
})
# 시간순으로 정렬 (오래된 것부터)
received_entries.reverse()
return received_entries
def visualize_email_route(route_data):
"""이메일 라우팅 경로 시각화"""
print("📧 이메일 라우팅 경로 분석")
print("=" * 50)
for i, hop in enumerate(route_data, 1):
print(f"\\n🏃 홉 {i}:")
print(f" 📤 발신서버: {hop['from_server']}")
print(f" 🌐 발신IP: {hop['from_ip']}")
print(f" 📥 수신서버: {hop['to_server']}")
print(f" ⏰ 시간: {hop['raw_timestamp']}")
# IP 지역 정보 (실제로는 GeoIP 라이브러리 사용)
location = get_ip_location(hop['from_ip'])
if location:
print(f" 📍 위치: {location}")
# 총 전송 시간 계산
if len(route_data) >= 2 and route_data[0]['timestamp'] and route_data[-1]['timestamp']:
total_time = route_data[-1]['timestamp'] - route_data[0]['timestamp']
print(f"\\n⏱️ 총 전송 시간: {total_time}")
def get_ip_location(ip_address):
"""IP 주소의 지역 정보 반환 (예시)"""
# 실제로는 geoip2, ipinfo.io API 등을 사용
import socket
try:
hostname = socket.gethostbyaddr(ip_address)[0]
return f"{hostname}"
except:
return "위치 정보 없음"
# 사용 예시
sample_headers = """
Received: from mail.example.com (mail.example.com [203.0.113.10])
by mx1.gmail.com (Postfix) with ESMTP id 5B2C31040B2A
for <user@gmail.com>; Mon, 15 Jan 2024 14:30:15 +0900
Received: from webserver.internal ([192.168.1.100])
by mail.example.com (Postfix) with ESMTP id 3A1B20F8DA
for <user@gmail.com>; Mon, 15 Jan 2024 14:30:10 +0900
"""
route = parse_received_headers(sample_headers)
visualize_email_route(route)
🌍 지역별 라우팅 이상 탐지
비정상적인 라우팅 패턴
def detect_routing_anomalies(route_data):
"""라우팅 이상 패턴 탐지"""
anomalies = []
# 1. 지역 점프 탐지 (예: 한국→러시아→미국→한국)
locations = []
for hop in route_data:
location = get_country_from_ip(hop['from_ip'])
locations.append(location)
# 연속된 국가 변화가 3회 이상이면 의심
country_changes = 0
for i in range(1, len(locations)):
if locations[i] != locations[i-1]:
country_changes += 1
if country_changes >= 3:
anomalies.append(f"⚠️ 비정상적인 국가 간 라우팅: {' → '.join(locations)}")
# 2. 시간 역행 탐지
for i in range(1, len(route_data)):
if route_data[i]['timestamp'] and route_data[i-1]['timestamp']:
if route_data[i]['timestamp'] < route_data[i-1]['timestamp']:
anomalies.append("⚠️ 시간 역행 감지 (조작 가능성)")
# 3. 과도한 홉 수
if len(route_data) > 10:
anomalies.append(f"⚠️ 과도한 홉 수