피싱 수사를 위한 이메일 헤더 분석
사용 시기
- 피싱 의심 이메일을 조사하여 실제 발신자를 확인할 때
- 발신자 진위 확인 및 이메일 스푸핑 탐지 시
- 사용자가 피싱 링크를 클릭한 사고 대응 중
- 의심스러운 이메일의 전달 경로 및 중계 서버 추적 시
- SPF, DKIM, DMARC 정합성을 검증하여 위조 식별 시
전제 조건
- 의심스러운 메시지의 원시 이메일 헤더 (EML 또는 MSG 형식)
- SMTP 프로토콜 및 이메일 헤더 필드에 대한 이해
- SPF/DKIM/DMARC 확인을 위한 DNS 조회 도구 (dig, nslookup) 접근 권한
- 이메일 헤더 분석 도구 (MHA, emailheaders.net 개념)
- 자동 분석을 위한 Python 이메일 파싱 라이브러리
- IP/도메인 평판 조회를 위한 위협 인텔리전스 플랫폼 접근 권한
워크플로우
1단계: 원시 이메일 헤더 추출
# Outlook에서 내보내기: 이메일 열기 > 파일 > 속성 > 인터넷 헤더
# Gmail에서 내보내기: 이메일 열기 > 점 세 개 > 원본 보기
# Thunderbird에서 내보내기: 보기 > 메시지 소스
# 포렌식 이미지에서 EML 파일 사용 시
cp /mnt/evidence/Users/suspect/AppData/Local/Microsoft/Outlook/phishing_email.eml \
/cases/case-2024-001/email/
# PST 파일 사용 시 개별 메시지 추출
pip install pypff
python3 << 'PYEOF'
import pypff
pst = pypff.file()
pst.open("/cases/case-2024-001/email/outlook.pst")
root = pst.get_root_folder()
def extract_messages(folder, path=""):
for i in range(folder.get_number_of_sub_messages()):
msg = folder.get_sub_message(i)
headers = msg.get_transport_headers()
subject = msg.get_subject()
if headers:
filename = f"/cases/case-2024-001/email/msg_{i}_{subject[:30]}.txt"
with open(filename, 'w') as f:
f.write(headers)
for i in range(folder.get_number_of_sub_folders()):
extract_messages(folder.get_sub_folder(i))
extract_messages(root)
PYEOF
2단계: 이메일 헤더 체인 파싱
# Python 이메일 라이브러리로 헤더 파싱
python3 << 'PYEOF'
import email
from email import policy
with open('/cases/case-2024-001/email/phishing_email.eml', 'r') as f:
msg = email.message_from_file(f, policy=policy.default)
print("=== 주요 헤더 필드 ===")
print(f"보낸 사람: {msg['From']}")
print(f"받는 사람: {msg['To']}")
print(f"제목: {msg['Subject']}")
print(f"날짜: {msg['Date']}")
print(f"메시지 ID: {msg['Message-ID']}")
print(f"답장 주소: {msg['Reply-To']}")
print(f"반송 경로: {msg['Return-Path']}")
print(f"메일러: {msg['X-Mailer']}")
print(f"원본 IP: {msg['X-Originating-IP']}")
print("\n=== 수신 헤더 (아래에서 위로 = 시간 순서) ===")
received_headers = msg.get_all('Received')
if received_headers:
for i, header in enumerate(reversed(received_headers)):
print(f"\n홉 {i+1}: {header.strip()}")
print("\n=== 인증 결과 ===")
auth_results = msg.get_all('Authentication-Results')
if auth_results:
for result in auth_results:
print(result)
print(f"\nARC 인증 결과: {msg.get('ARC-Authentication-Results', '없음')}")
print(f"수신 SPF: {msg.get('Received-SPF', '없음')}")
print(f"DKIM 서명: {msg.get('DKIM-Signature', '없음')}")
PYEOF
3단계: SPF, DKIM, DMARC 레코드 검증
# 봉투 발신자 도메인 추출
SENDER_DOMAIN="example-corp.com"
# SPF 레코드 확인
dig TXT $SENDER_DOMAIN +short | grep "v=spf1"
# 예: "v=spf1 include:_spf.google.com include:sendgrid.net ~all"
# DKIM 레코드 확인 (DKIM-Signature 헤더에서 셀렉터 예: "s=selector1")
DKIM_SELECTOR="selector1"
dig TXT ${DKIM_SELECTOR}._domainkey.${SENDER_DOMAIN} +short
# DMARC 레코드 확인
dig TXT _dmarc.${SENDER_DOMAIN} +short
# 예: "v=DMARC1; p=reject; rua=mailto:[email protected]; pct=100"
# 발신 IP를 SPF에 대해 검증
# 첫 번째 수신 헤더에서 IP 추출
SENDING_IP="203.0.113.45"
# Python으로 SPF 수동 확인
python3 << 'PYEOF'
import spf # pip install pyspf
result, explanation = spf.check2(
i='203.0.113.45',
s='[email protected]',
h='mail.example-corp.com'
)
print(f"SPF 결과: {result}")
print(f"설명: {explanation}")
# 결과: pass, fail, softfail, neutral, none, temperror, permerror
PYEOF
# 발신 IP가 알려진 악성 IP 목록에 있는지 확인
# AbuseIPDB 또는 VirusTotal 쿼리
curl -s "https://api.abuseipdb.com/api/v2/check?ipAddress=${SENDING_IP}" \
-H "Key: YOUR_API_KEY" -H "Accept: application/json" | python3 -m json.tool
4단계: 발신자 도메인 및 인프라 분석
# 발신자 도메인 WHOIS 조회
whois $SENDER_DOMAIN | grep -iE '(registrar|creation|expiration|registrant|nameserver)'
# 도메인 기간 확인 (최근 등록된 도메인은 의심스러움)
# DNS 레코드 조사
dig A $SENDER_DOMAIN +short
dig MX $SENDER_DOMAIN +short
dig NS $SENDER_DOMAIN +short
# 발신 IP의 역방향 DNS
dig -x $SENDING_IP +short
# 유사/오타 도메인 확인
# 시각적 유사성을 이용해 정식 도메인과 비교
python3 << 'PYEOF'
import Levenshtein # pip install python-Levenshtein
legitimate = "microsoft.com"
suspicious = "micr0soft.com"
distance = Levenshtein.distance(legitimate, suspicious)
ratio = Levenshtein.ratio(legitimate, suspicious)
print(f"편집 거리: {distance}")
print(f"유사도 비율: {ratio:.2%}")
if ratio > 0.8:
print("경고: 오타 도메인/유사 도메인 가능성 높음!")
PYEOF
# VirusTotal에서 도메인 평판 확인
curl -s "https://www.virustotal.com/api/v3/domains/${SENDER_DOMAIN}" \
-H "x-apikey: YOUR_VT_API_KEY" | python3 -m json.tool
# Reply-To가 From과 다른지 확인 (일반적인 피싱 지표)
python3 -c "
import email
with open('/cases/case-2024-001/email/phishing_email.eml') as f:
msg = email.message_from_file(f)
from_addr = email.utils.parseaddr(msg['From'])[1]
reply_to = email.utils.parseaddr(msg.get('Reply-To', msg['From']))[1]
if from_addr != reply_to:
print(f'경고: From ({from_addr}) != Reply-To ({reply_to})')
else:
print('From과 Reply-To 일치')
"
5단계: 이메일 본문 및 첨부 파일 검사
# 이메일 본문에서 URL 추출
python3 << 'PYEOF'
import email
import re
from email import policy
with open('/cases/case-2024-001/email/phishing_email.eml', 'r') as f:
msg = email.message_from_file(f, policy=policy.default)
body = msg.get_body(preferencelist=('html', 'plain'))
if body:
content = body.get_content()
urls = re.findall(r'https?://[^\s<>\"\']+', content)
print("=== 이메일 본문에서 발견된 URL ===")
for url in set(urls):
print(f" {url}")
# URL 난독화 확인 (표시 텍스트 != href)
href_pattern = re.findall(r'<a[^>]*href=[\"\']([^\"\']+)[\"\'][^>]*>(.*?)</a>', content, re.DOTALL)
print("\n=== 하이퍼링크 분석 ===")
for href, text in href_pattern:
display_url = re.findall(r'https?://[^\s<]+', text)
if display_url and display_url[0] != href:
print(f" 불일치: 표시='{display_url[0]}' -> 실제='{href}'")
# 첨부 파일 추출 및 해시 계산
print("\n=== 첨부 파일 ===")
for part in msg.walk():
if part.get_content_disposition() == 'attachment':
filename = part.get_filename()
content = part.get_payload(decode=True)
import hashlib
sha256 = hashlib.sha256(content).hexdigest()
print(f" 파일: {filename}, 크기: {len(content)}, SHA-256: {sha256}")
with open(f'/cases/case-2024-001/email/attachments/{filename}', 'wb') as af:
af.write(content)
PYEOF
# 첨부 파일 해시를 VirusTotal에 제출
# URL을 URLhaus 또는 PhishTank에 제출하여 평판 확인
주요 개념
| 개념 | 설명 |
|---|---|
| SPF (Sender Policy Framework) | 도메인이 승인한 메일 서버를 지정하는 DNS 레코드 |
| DKIM (DomainKeys Identified Mail) | 이메일 콘텐츠 무결성을 확인하는 암호화 서명 |
| DMARC | SPF와 DKIM을 결합한 발신자 인증 정책 프레임워크 |
| 수신 헤더 | 전달 체인의 각 홉을 보여주는 서버 추가 헤더 (하단에서 상단 순서로 읽음) |
| Return-Path | 반송 메시지에 사용되는 봉투 발신자 주소; From과 다를 수 있음 |
| Message-ID | 발신 메일 서버가 할당한 고유 식별자 |
| X-Originating-IP | 원본 발신자 IP 주소 (일부 메일 서비스에서 추가) |
| 헤더 위조 | 공격자는 From, Reply-To 등 헤더를 위조할 수 있지만 수신 체인은 위조 불가 |
도구 및 시스템
| 도구 | 목적 |
|---|---|
| MXToolbox | 온라인 이메일 헤더 분석기 및 DNS 조회 |
| dig/nslookup | SPF, DKIM, DMARC 확인을 위한 DNS 레코드 쿼리 |
| pyspf | Python SPF 레코드 검증 라이브러리 |
| dkimpy | Python DKIM 서명 검증 라이브러리 |
| PhishTool | 전문 피싱 이메일 분석 플랫폼 |
| VirusTotal | URL 및 파일 평판 검사 서비스 |
| AbuseIPDB | IP 주소 평판 데이터베이스 |
| whois | 도메인 등록 정보 조회 |
일반적인 시나리오
시나리오 1: CEO 사기 / 기업 이메일 침해 이메일이 CEO로부터 온 것처럼 위장했지만 Reply-To가 Gmail 주소를 가리키고, SPF는 발신 IP가 스푸핑된 도메인에 대해 승인되지 않아 실패하며, DKIM이 누락되고, From 도메인이 유사 도메인(ceo-company.com vs company.com)인 경우.
시나리오 2: 자격 증명 탈취 피싱 이메일에 "login.microsoft.com"을 표시하지만 href는 유사 도메인을 가리키는 링크가 포함되어 있고, 첨부 파일이 가짜 로그인 페이지(자격 증명 유출 JavaScript 포함)인 HTML 파일이며, 발신 도메인이 3일 전에 등록된 경우.
시나리오 3: 첨부 파일을 통한 악성코드 전달 매크로가 포함된 Office 문서 첨부 파일이 포함된 이메일, 발신자 도메인이 SPF를 통과했지만 계정이 손상되었고, DKIM 서명이 유효하며(정식 인프라에서 전송됨), 첨부 파일 SHA-256이 VirusTotal에서 알려진 악성코드와 일치하는 경우.
시나리오 4: 정식 서비스를 이용한 표적 피싱 공격자가 합법적인 이메일 마케팅 서비스를 사용하여 피싱 이메일을 전송, SPF와 DKIM이 서비스 승인으로 통과하고, 피싱이 인프라가 아닌 콘텐츠에 존재하므로 헤더 인증 확인보다 URL 및 콘텐츠 분석이 필요한 경우.
출력 형식
이메일 헤더 분석 보고서:
제목: "긴급: 인보이스 결제 요망"
보낸 사람: [email protected] (스푸핑됨)
답장 주소: [email protected] (불일치)
반송 경로: <[email protected]>
날짜: 2024-01-15 09:23:45 UTC
전달 경로 (4홉):
홉 1: mail-server.xyz [203.0.113.45] -> relay1.isp.com
홉 2: relay1.isp.com -> mx.target-company.com
홉 3: mx.target-company.com -> internal-filter.target.com
홉 4: internal-filter.target.com -> 메일함
인증:
SPF: 실패 (203.0.113.45가 examp1e-corp.com에 대해 승인되지 않음)
DKIM: 없음 (서명 없음)
DMARC: 실패 (p=none, 적용 없음)
피싱 지표:
- 유사 도메인 (examp1e-corp.com vs example-corp.com, 96% 유사)
- From/Reply-To 불일치
- 이메일 전송 2일 전 등록된 도메인
- 본문 URL이 자격 증명 탈취 페이지를 가리킴
- 첨부 파일: invoice.xlsm (SHA-256: a3f2...) - VT에서 알려진 악성코드
위험 수준: 높음