社会工程学攻击取证深度分析
0x01 社会工程学攻击概述与取证框架
社会工程学攻击分类体系
社会工程学(Social Engineering)是利用人类心理弱点、认知偏差和行为习惯,通过欺骗、操纵等手段获取敏感信息、访问权限或执行特定动作的攻击方式。与传统技术攻击不同,社会工程学攻击的核心目标是人而非系统。
攻击类型分层矩阵:
| 攻击类型 | 投递渠道 | 目标角色 | 技术复杂度 | 成功概率 | 典型损失 |
|---|---|---|---|---|---|
| 大规模钓鱼(Phishing) | 邮件/SMS | 普通员工 | 低 | 低(0.1-2%) | 单账号凭证 |
| 鱼叉式钓鱼(Spear Phishing) | 邮件 | 特定人员 | 中 | 中(5-15%) | 内网入口 |
| 鲸钓(Whaling) | 邮件/电话 | C-level高管 | 高 | 中(10-20%) | 巨额资金 |
| BEC 商务邮件欺诈 | 邮件 | 财务/采购 | 高 | 高(20-40%) | 平均$150K+ |
| Vishing 语音钓鱼 | 电话/VoIP | IT支持/HR | 中 | 中(15-30%) | 凭据/权限 |
| Smishing 短信钓鱼 | SMS/iMessage | 手机用户 | 低 | 低(3-5%) | 恶意App安装 |
| 物理社工(Physical SE) | 现场入侵 | 安保/前台 | 高 | 中高(30-50%) | 物理接入/设备 |
| 水坑攻击(Watering Hole) | Web站点 | 特定组织成员 | 很高 | 低(1-3%) | 供应链入口 |
现代攻击链中的社会工程学定位:
在 APT 攻击全生命周期中,社会工程学通常出现在初始突破阶段(Initial Access),是最常见的边界突破手段。根据 Verizon DBIR 2024 报告,约 68% 的数据泄露涉及人为交互环节,其中钓鱼邮件占据主导。攻击者选择社工路径的根本原因在于:相比零日漏洞利用,人的弱点更稳定、更可预测、且防御成本更高。
MITRE ATT&CK 社会工程学技术映射:
MITRE_ATT&CK_Social_Engineering_Mapping:
Initial_Access:
- T1566.001: Spearphishing Attachment(鱼叉式钓鱼附件)
- T1566.002: Spearphishing Link(鱼叉式钓鱼链接)
- T1566.003: Spearphishing via Service(通过第三方服务钓鱼)
- T1566.004: Spearphishing Voice Call(鱼叉式语音通话)
Credential_Access:
- T1598.001: Phishing for Credential - Spearphishing Service
- T1598.002: Spearphishing Attachment (Credential Harvesting)
- T1598.003: Spearphishing Link (Credential Harvesting)
Collection:
- T1534: Internal Spearphishing(内部鱼叉式钓鱼)
Reconnaissance:
- T1592: Gather Victim Host Information
- T1593: Search Open Websites/Databases
- T1594: Search Victim-Owned Websites
- T1595: Active Scanning
- T1596: Search Open Technical Databases
- T1597: Search Closed Sources
Resource_Development:
- T1583.006: Acquire Infrastructure - Web Services
- T1586: Compromise Accounts
- T1586.002: Email Accounts
- T1650: Acquire Access取证挑战与方法论
社会工程学攻击取证面临三大核心挑战:
证据分散性:一封钓鱼邮件可能涉及的证据分布于邮件网关日志、DNS 记录、WHOIS 历史、CDN 缓存、终端浏览器历史、AD 认证日志、代理服务器日志、防火墙流量等多个独立系统中,这些系统往往属于不同管理部门,证据采集需要跨团队协调。
跨平台性:攻击链可能横跨邮件系统(Exchange/O365/Gmail)、即时通讯(Teams/Slack/WeChat)、电话系统(VoIP/PSTN)、物理门禁、银行系统等多个异构平台,每个平台的日志格式、保留周期、提取方式各不相同。
法律边界:OSINT 调查必须在法律允许范围内进行。对攻击者基础设施的主动探测(端口扫描、漏洞利用)可能触犯计算机犯罪相关法律;跨境调查涉及数据隐私法规(GDPR/CCPA/个保法)冲突;暗网监控需要注意蜜罐合法性问题。
取证方法论四步框架:
FORENSIC_FRAMEWORK = {
"Phase_1_Timeline_Reconstruction": {
"goal": "重建完整攻击时间线",
"steps": [
"收集所有相关日志源(邮件头/服务器日志/终端日志/网络流量)",
"统一时间戳为UTC并标注时区偏移",
"按时间排序构建事件序列",
"识别时间窗口内的异常行为节点"
]
},
"Phase_2_Infrastructure_Correlation": {
"goal": "关联攻击基础设施组件",
"steps": [
"提取所有可疑域名/IP/URL",
"WHOIS + DNS + SSL证书交叉查询",
"被动DNS数据库验证历史解析关系",
"构建基础设施拓扑图"
]
},
"Phase_3_Psychological_Profiling": {
"goal": "构建攻击者心理画像",
"steps": [
"分析话术模板语言特征",
"识别文化背景暗示(节日/时区/语言习惯)",
"评估攻击定制化程度",
"推断攻击者组织能力层级"
]
},
"Phase_4_Evidence_Gradation": {
"goal": "证据强度分层评估",
"levels": [
"确认恶意(Confirmed Malicious): 可直接证明恶意意图的证据",
"高度可疑(Highly Suspicious): 强烈暗示恶意但需佐证的证据",
"需要关注(Requires Attention): 存在异常但无法直接归因的指标"
]
}
}0x02 钓鱼邮件基础设施溯源
邮件头深度分析
邮件头是钓鱼邮件事后取证的第一个切入点,包含完整的邮件路由信息、认证结果和内容元数据。
完整路由追踪方法:
以一封典型的鱼叉式钓鱼邮件为例,从 Received 头部逐层逆序解析:
完整邮件头示例分析:
Return-Path: <no-reply@micros0ft-security.com>
Delivered-To: target@company.com
Received: from mail.company.com (10.0.1.25) by exchange.company.com
with SMTP; Mon, 15 Jan 2026 08:23:41 +0800
Received: from mx-relay.external.net (203.0.113.50) by mail.company.com
with ESMTPS; Mon, 15 Jan 2026 08:23:39 +0800
Received: from [198.51.100.23] (helo=sender.campaign-server.io)
by mx-relay.external.net with SMTP; Mon, 15 Jan 2026 00:23:35 +0000
From: "Microsoft Security Team" <no-reply@micros0ft-security.com>
Reply-To: security-alerts@ms-support-verify.com
Subject: Urgent: Your Account Requires Immediate Verification
X-Mailer: PHPMailer v6.8.0
X-Originating-IP: [198.51.100.23]关键取证要点:
- 最顶部的
Received是最接近收件方的跳,最底部是原始发送方 HELO/EHLO hostname与实际 IP 不匹配是重要红旗指标Reply-To与From地址不一致是高概率钓鱼特征X-Originating-IP可能泄露真实发送 IP(但常被清除或伪造)X-Mailer字段可关联已知攻击工具指纹
延迟分析技术:
每跳之间的时间差可以揭示中继服务器的地理位置分布和转发模式:
import email
from datetime import datetime
import re
def parse_email_headers(raw_headers):
msg = email.message_from_string(raw_headers)
received_headers = msg.get_all('Received')
hops = []
for header in reversed(received_headers):
timestamp_match = re.search(r';\s*(.+)$', header)
if timestamp_match:
time_str = timestamp_match.group(1).strip()
try:
parsed_time = email.utils.parsedate_to_datetime(time_str)
hops.append({
'hop_raw': header.split(';')[0].strip(),
'timestamp': parsed_time,
'timezone': parsed_time.tzinfo
})
except Exception:
continue
analysis = []
for i in range(1, len(hops)):
delay = (hops[i]['timestamp'] - hops[i-1]['timestamp']).total_seconds()
analysis.append({
'from_hop': i-1,
'to_hop': i,
'delay_seconds': delay,
'anomaly': True if abs(delay) > 30 or delay < 0 else False
})
return analysisX-Header 分析清单:
| X-Header | 取证价值 | 常见伪造方式 |
|---|---|---|
| X-Originating-IP | 原始发送IP | 可能被中间件删除或修改 |
| X-Mailer | 发件客户端软件 | 攻击工具签名识别 |
| X-Spam-Status | 反垃圾引擎判定 | 绕过SPF后可能标记clean |
| X-SES-Outgoing | AWS SES发送标识 | 确认云服务滥用 |
| X-Microsoft-Antispam | Exchange Online过滤结果 | 反映微软信誉评分 |
| X-EOPAttributedMessage | EOP处理标记 | 区分外部/内部消息 |
| X-MS-Exchange-Organization-AuthAs | 认证身份 | Internal vs Anonymous |
发件人域名分析
WHOIS 历史查询深度分析:
使用多种工具交叉验证域名注册信息:
whois micros0ft-security.com
Domain Name: MICROS0FT-SECURITY.COM
Registry Domain ID: 2845671234_DOMAIN_COM-vrsn
Registrar WHOIS Server: whois.namecheap.com
Registrar: NameCheap, Inc.
Creation Date: 2025-12-28T09:15:00Z
Updated Date: 2025-12-28T09:15:00Z
Expiry Date: 2027-12-28T09:15:00Z
Registrant Organization: PrivacyProtect.org
Registrant State/Province: MA
Registrant Country: US取证分析要点:
- 注册日期距离攻击发生仅 18 天 → 新注册域名,高风险指标
- 使用隐私保护服务 → 无法直接获取注册人信息
- 选择低价注册商(NameCheap)→ 常见于短期攻击基础设施
- 有效期仅 2 年 → 预期使用时间短暂
DNS 记录深度分析:
dig micros0ft-security.com ANY +noall +answer
micros0ft-security.com. 300 IN A 198.51.100.23
micros0ft-security.com. 300 IN MX 10 mail.micros0ft-security.com.
micros0ft-security.com. 300 IN TXT "v=spf1 include:_spf.mailgun.org ~all"
mail._domainkey.micros0ft-security.com. 300 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSq..."
dig micros0ft-security.com NS +short
ns1.suspended-domain.com.
ns2.suspended-domain.com.
dig _dmarc.micros0ft-security.com TXT +short
"v=DMARC1; p=none; rua=mailto:dmarc@micros0ft-security.com"DNS 配置取证结论:
| DNS 记录 | 观察结果 | 风险等级 | 说明 |
|---|---|---|---|
| A Record | 指向VPS IP | 🔴 高 | 非企业级基础设施 |
| SPF | 使用Mailgun | 🟡 中 | 合法服务可被滥用 |
| DKIM | 已配置 | 🟢 低 | 提高邮件可信度 |
| DMARC | p=none | 🔴 高 | 未启用拒绝策略 |
| NS | suspended-domain.com | 🔴 极高 | 域名曾被暂停/滥用 |
相似域名检测(Homoglyph / Typosquatting):
import dns.resolver
from itertools import permutations
def generate_typosquat_variants(domain):
name, tld = domain.rsplit('.', 1)
variants = set()
for i in range(len(name)):
variants.add(name[:i] + name[i+1:])
char_substitutions = {
'o': '0', '0': 'o', 'l': '1', '1': 'l',
'e': '3', 'a': '@', 's': 'z', 'rn': 'm',
'cl': 'd', 'vv': 'w'
}
for original, replacement in char_substitutions.items():
if original in name:
variants.add(name.replace(original, replacement))
for i in range(len(name) - 1):
variants.add(name[:i] + name[i+1:i+2] + name[i:i+1] + name[i+2:])
for sep in ['-', '.']:
for i in range(1, len(name)):
variants.add(name[:i] + sep + name[i:])
homoglyph_map = {
'a': 'а', 'c': 'ϲ', 'e': 'е', 'o': 'ο',
'p': 'р', 's': 'ѕ', 'x': 'х'
}
for orig, hglyph in homoglyph_map.items():
if orig in name:
variants.add(name.replace(orig, hglyph))
return [v + '.' + tld for v in variants]
def check_domain_resolved(domains):
resolved = {}
resolver = dns.resolver.Resolver()
resolver.nameservers = ['8.8.8.8']
resolver.lifetime = 3
for domain in domains:
try:
answers = resolver.resolve(domain, 'A')
ips = [rdata.address for rdata in answers]
resolved[domain] = ips
except Exception:
resolved[domain] = None
return resolved邮件模板分析
HTML 结构指纹提取:
from bs4 import BeautifulSoup
import hashlib
def extract_template_fingerprint(html_content):
soup = BeautifulSoup(html_content, 'html.parser')
structure_hash = hashlib.sha256()
for tag in soup.descendants:
if tag.name:
structure_hash.update(tag.name.encode())
fingerprint = structure_hash.hexdigest()[:16]
styles = soup.find_all(['style', 'link'], rel='stylesheet')
style_content = ''.join(s.get_text() for s in soup.find_all('style'))
style_hash = hashlib.md5(style_content.encode()).hexdigest()[:12]
images = [img.get('src', '') for img in soup.find_all('img')]
tracking_pixels = [src for src in images if 'track' in src.lower() or 'pixel' in src.lower()]
links = [a.get('href', '') for a in soup.find_all('a')]
suspicious_links = [l for l in links if l.startswith('http') and 'target' not in str(soup.find('a', href=l))]
return {
'structure_fingerprint': fingerprint,
'style_fingerprint': style_hash,
'tracking_pixels': tracking_pixels,
'suspicious_links': suspicious_links,
'total_images': len(images),
'total_links': len(links),
'form_count': len(soup.find_all('form')),
'iframe_count': len(soup.find_all('iframe'))
}图片 EXIF 数据分析:
exiftool phishing_logo.png
ExifTool Version Number : 12.70
File Name : phishing_logo.png
File Size : 45 KB
File Modification Date/Time : 2025:12:20 14:32:18+08:00
File Type : PNG
Image Width : 200
Image Height : 56
Software : Adobe Photoshop 25.3
Creator Tool : Canva
Color Space : sRGBEXIF 中暴露了 Canva 作为设计工具 → 攻击者使用了在线设计平台制作钓鱼素材,这意味着 Canva 平台上可能存在该攻击者的账户和活动记录。
落地页取证
页面源码分析与表单提交目标提取:
import requests
from bs4 import BeautifulSoup
import re
def analyze_landing_page(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers, allow_redirects=False, timeout=15)
redirect_chain = [url]
while response.is_redirect or response.status_code in [301, 302, 303, 307, 308]:
next_url = response.headers.get('Location')
redirect_chain.append(next_url)
response = requests.get(next_url, headers=headers, allow_redirects=False, timeout=15)
final_html = response.text
soup = BeautifulSoup(final_html, 'html.parser')
forms = []
for form in soup.find_all('form'):
form_data = {
'action': form.get('action', ''),
'method': form.get('method', 'GET').upper(),
'fields': [],
'javascript_submit': bool(re.search(r'\.submit\(\)|\.click\(\)', final_html))
}
for inp in form.find_all(['input', 'textarea', 'select']):
form_data['fields'].append({
'name': inp.get('name', ''),
'type': inp.get('type', 'text'),
'id': inp.get('id', ''),
'placeholder': inp.get('placeholder', ''),
'autocomplete': inp.get('autocomplete', '')
})
forms.append(form_data)
scripts = []
for script in soup.find_all('script'):
src = script.get('src', '')
if src:
scripts.append({'external': src})
elif script.string:
scripts.append({'inline': script.string[:500]})
meta_tags = {}
for meta in soup.find_all('meta'):
name = meta.get('name', meta.get('property', ''))
content = meta.get('content', '')
if name:
meta_tags[name] = content
return {
'final_url': response.url,
'redirect_chain': redirect_chain,
'status_code': response.status_code,
'server_header': response.headers.get('Server', ''),
'forms': forms,
'scripts': scripts,
'meta_tags': meta_tags,
'tls_info': {
'issuer': str(response.raw.connection.sock.getpeercert().get('issuer', '')),
'subject': str(response.raw.connection.sock.getpeercert().get('subject', ''))
} if hasattr(response.raw, 'connection') and response.raw.connection else None
}Wayback Machine 历史查询:
curl -s "https://archive.org/wayback/available?url=suspicious-site.com" | python3 -m json.tool
{
"archived_snapshots": {
"closest": {
"available": true,
"url": "https://web.archive.org/web/20260110/https://suspicious-site.com",
"timestamp": "20260110",
"status": "200"
}
}
}
检查页面是否曾经存在合法内容后被替换为钓鱼页面:
curl -s "https://web.archive.org/web/*/suspicious-site.com/*" | grep -oP 'datetime="[^"]*"'Python 脚本:邮件头自动解析与基础设施提取
#!/usr/bin/env python3
import email
import re
import json
import hashlib
from datetime import datetime, timezone
import dns.resolver
import requests
class PhishingEmailAnalyzer:
def __init__(self):
self.resolver = dns.resolver.Resolver()
self.resolver.nameservers = ['8.8.8.8', '1.1.1.1']
self.resolver.lifetime = 5
self.extracted_iocs = {
'domains': set(),
'ips': set(),
'urls': set(),
'emails': set(),
'hashes': set()
}
def parse_full_headers(self, raw_message):
msg = email.message_from_string(raw_message)
auth_results = {}
for header_name in ['Authentication-Results', 'X-Microsoft-Antispam',
'X-SES-Outgoing', 'X-Original-Sender']:
value = msg.get(header_name)
if value:
auth_results[header_name] = value
received_chain = []
for header in msg.get_all('Received', []):
received_chain.append(header)
hop_details = []
for i, hop in enumerate(reversed(received_chain)):
ip_match = re.findall(r'\[(\d+\.\d+\.\d+\.\d+)\]', hop)
host_match = re.search(r'from\s+(\S+)', hop)
time_match = re.search(r';\s*(.+)$', hop)
hop_info = {
'sequence': i + 1,
'raw': hop.strip(),
'ips': ip_match,
'hostname': host_match.group(1) if host_match else '',
'timestamp': time_match.group(1).strip() if time_match else ''
}
hop_details.append(hop_info)
for ip in ip_match:
self.extracted_iocs['ips'].add(ip)
sender_analysis = {
'from': msg.get('From', ''),
'reply_to': msg.get('Reply-To', ''),
'return_path': msg.get('Return-Path', ''),
'envelope_from_mismatch': msg.get('Reply-To', '') != msg.get('From', '')
}
url_pattern = re.compile(r'https?://[^\s<>"\'`)]+')
email_pattern = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+')
body = ''
if msg.is_multipart():
for part in msg.walk():
ct = part.get_content_type()
if ct in ['text/html', 'text/plain']:
body += part.get_payload(decode=True).decode('utf-8', errors='ignore')
else:
body = msg.get_payload(decode=True).decode('utf-8', errors='ignore')
for match in url_pattern.finditer(body):
self.extracted_iocs['urls'].add(match.group(0))
for match in email_pattern.finditer(body):
self.extracted_iocs['emails'].add(match.group(0))
display_urls = []
anchor_pattern = re.compile(r'<a[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)</a>', re.DOTALL)
for match in anchor_pattern.finditer(body):
real_url = match.group(1)
display_text = re.sub(r'<[^>]+>', '', match.group(2)).strip()
if display_text and 'http' in display_text:
display_urls.append({
'display_text': display_text,
'real_url': real_url,
'mismatch': display_text != real_url
})
return {
'authentication': auth_results,
'hop_chain': hop_details,
'sender': sender_analysis,
'display_url_mismatches': display_urls,
'iocs': {k: list(v) for k, v in self.extracted_iocs.items()}
}
def enrich_domains(self):
enrichment = {}
for domain in list(self.extracted_iocs['domains']):
info = {'domain': domain}
try:
whois_resp = requests.get(f'https://www.whoisxmlapi.com/whoisserver/WhoisService?domain={domain}&outputFormat=json', timeout=10)
info['whois_available'] = whois_resp.status_code == 200
except Exception:
info['whois_available'] = False
try:
ns_records = self.resolver.resolve(domain, 'NS')
info['nameservers'] = [str(r) for r in ns_records]
except Exception:
info['nameservers'] = []
try:
mx_records = self.resolver.resolve(domain, 'MX')
info['mx_records'] = [(str(r.exchange), r.preference) for r in mx_records]
except Exception:
info['mx_records'] = []
try:
spf_record = self.resolver.resolve(domain, 'TXT')
spf_texts = [str(r).strip('"') for r in spf_record]
info['spf'] = [t for t in spf_texts if 'v=spf1' in t]
except Exception:
info['spf'] = []
enrichment[domain] = info
return enrichment
def generate_report(self, header_analysis, domain_enrichment):
report = {
'analysis_timestamp': datetime.now(timezone.utc).isoformat(),
'email_authentication': header_analysis['authentication'],
'routing_chain': header_analysis['hop_chain'],
'sender_analysis': header_analysis['sender'],
'display_url_deception': header_analysis['display_url_mismatches'],
'infrastructure_enrichment': domain_enrichment,
'extracted_iocs': header_analysis['iocs'],
'risk_indicators': [],
'attribution_hints': []
}
if header_analysis['sender']['envelope_from_mismatch']:
report['risk_indicators'].append('Reply-To 与 From 地址不一致')
for mismatch in header_analysis['display_url_mismatches']:
if mismatch['mismatch']:
report['risk_indicators'].append(f'URL显示欺骗: 显示"{mismatch["display_text"]}" → 实际"{mismatch["real_url"]}"')
for domain, info in domain_enrichment.items():
if info.get('nameservers'):
for ns in info['nameservers']:
if any(suspect in ns.lower() for suspect in ['suspended', 'parked', 'expired']):
report['risk_indicators'].append(f'{domain} 使用可疑NS: {ns}')
return report0x03 BEC 商务邮件欺诈取证
BEC 攻击模式分类
BEC(Business Email Compromise) 是社会工程学攻击中经济损失最大的一类。FBI IC3 报告显示,2024 年 BEC 造成的全球损失超过 $29 亿,平均每起事件损失 $150,000 以上。
四大 BEC 攻击模式对比:
| 攻击模式 | 伪装对象 | 目标部门 | 典型话术 | 平均响应时间要求 |
|---|---|---|---|---|
| CEO Fraud | CEO/CFO | 财务/会计 | “紧急转账,保密” | 1-2小时 |
| Vendor Fraud | 供应商 | 应付账款 | “更换收款账户” | 24-48小时 |
| Attorney Impersonation | 法务/外部律师 | 法务部/管理层 | “机密并购项目” | 数小时 |
| IT Support Fraud | IT管理员 | 全员 | “密码即将过期” | 即时 |
邮件账户入侵检测
邮箱规则异常检测 — 隐藏转发规则取证:
PowerShell 检查 O365 邮箱异常规则:
Get-InboxRule -Mailbox victim@company.com | Select-Object Name, Enabled, Description, Priority | Format-List
异常规则特征:
Name : "Process Incoming Mail"(伪装正常名称)
Enabled : True
Description : "将来自特定发件人的邮件标记为已读并移至RSS Feeds文件夹"
"同时将副本转发至 external@gmail.com"
Priority : -1(最高优先级,确保最先执行)
查找隐藏转发规则的命令:
Get-InboxRule -Mailbox victim@company.com | Where-Object {$_.ForwardTo -ne $null -or $_.RedirectTo -ne $null} | Format-List *登录日志异常分析:
Azure AD Sign-in Logs 异常查询:
SigninLogs
| where UserPrincipalName == "victim@company.com"
| project TimeGenerated, IPAddress, Location, DeviceDetail, ClientAppUsed, ConditionalAccessStatus, ResultDescription
| order by TimeGenerated desc
异常检测维度:
- Impossible Travel: 短时间内从两个地理上不可达的位置登录
- 新 IP/国家首次出现
- 异常设备指纹(之前从未出现的浏览器/OS组合)
- 非工作时间登录模式突变
- ConditionalAccessStatus 变化(绕过MFA尝试)OAUTH Token 滥用检测:
import msal
import requests
from datetime import datetime
class O365TokenAuditor:
def __init__(self, tenant_id, client_id, client_secret):
self.authority = f"https://login.microsoftonline.com/{tenant_id}"
self.app = msal.ConfidentialClientApplication(
client_id, authority=self.authority, client_credential=client_secret
)
self.graph_base = "https://graph.microsoft.com/v1.0"
def audit_oauth_consent(self):
token_result = self.app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
access_token = token_result['access_token']
headers = {'Authorization': f'Bearer {access_token}'}
apps_response = requests.get(
f"{self.graph_base}/servicePrincipals",
headers=headers,
params={"$filter": "tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')"}
).json()
suspicious_apps = []
for app in apps_response.get('value', []):
oauth_perms = app.get('oauth2PermissionScopes', [])
delegated_perms = app.get('oauth2PermissionGrants', [])
high_risk_scopes = ['Mail.Read', 'Mail.ReadWrite', 'Mail.Send',
'Files.Read.All', 'MailboxSettings.ReadWrite']
for perm in delegated_perms:
scope_list = perm.get('scope', '').split()
risky = [s for s in scope_list if s in high_risk_scopes]
if risky:
suspicious_apps.append({
'app_id': app.get('appId'),
'app_display_name': app.get('displayName'),
'publisher': app.get('publisherName'),
'risky_scopes': risky,
'consent_time': perm.get('startTime', 'unknown')
})
return suspicious_apps
def check_mailbox_delegation(self, user_upn):
token_result = self.app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
headers = {'Authorization': f'Bearer {token_result["access_token"]}'}
delegates = requests.get(
f"{self.graph_base}/users/{user_upn}/mailboxSettings",
headers=headers
).json()
return delegates财务流程分析
付款指令变更追溯:
在 BEC 案件中,财务流程的篡改取证是关键。取证人员需要还原完整的付款变更链路:
def trace_payment_instruction_change(internal_emails, bank_statements):
timeline = []
for email_record in internal_emails:
subject = email_record['subject']
keywords = ['bank', 'account', 'wire', 'payment', 'invoice', 'vendor',
'change', 'update', 'new account', 'replacement']
if any(kw in subject.lower() for kw in keywords):
timeline.append({
'timestamp': email_record['received_time'],
'event_type': 'potential_bank_change_request',
'sender': email_record['sender'],
'recipient': email_record['recipient'],
'subject': subject,
'has_attachment': bool(email_record.get('attachments')),
'urgency_indicators': count_urgency_words(email_record['body']),
'secrecy_requests': count_secrecy_words(email_record['body'])
})
for txn in bank_statements:
matching_events = [e for e in timeline
if abs((e['timestamp'] - txn['process_time']).total_seconds()) < 86400]
if matching_events:
txn['correlated_email_event'] = matching_events
return sorted(timeline, key=lambda x: x['timestamp'])
def count_urgency_words(text):
urgency_terms = ['urgent', 'immediately', 'asap', 'critical', 'confidential',
'do not share', 'secret', 'time-sensitive', 'rush', 'priority']
text_lower = text.lower()
return sum(1 for term in urgency_terms if term in text_lower)
def count_secrecy_words(text):
secrecy_terms = ['do not tell', 'keep quiet', 'between us', 'not announce',
'merger preparation', 'board approval pending', 'internal only']
text_lower = text.lower()
return sum(1 for term in secrecy_terms if term in text_lower)邮件通信模式异常检测
def detect_bec_communication_anomalies(mailbox_logs, baseline_period_days=30):
import numpy as np
normal_patterns = {}
for user, logs in mailbox_logs.items():
recent = logs[-baseline_period_days:]
baseline = logs[:-baseline_period_days]
baseline_external_ratio = np.mean([log['is_external'] for log in baseline])
recent_external_ratio = np.mean([log['is_external'] for log in recent])
baseline_outbound_hour = {}
for log in baseline:
hour = log['sent_time'].hour
baseline_outbound_hour.setdefault(hour, 0)
baseline_outbound_hour[hour] += 1
recent_outbound_hour = {}
for log in recent:
hour = log['sent_time'].hour
recent_outbound_hour.setdefault(hour, 0)
recent_outbound_hour[hour] += 1
thread_injection_candidates = []
for log in recent:
if log['in_reply_to'] and log['thread_position'] == 0:
original_thread_starter = find_original_sender(log['in_reply_to'], logs)
if original_thread_starter != log['sender'] and log['is_external']:
thread_injection_candidates.append(log)
anomalies = {
'external_ratio_spike': recent_external_ratio - baseline_external_ratio,
'off_hours_spike': sum(recent_outbound_hour.get(h, 0) for h in range(0, 7)),
'thread_injections': thread_injection_candidates
}
normal_patterns[user] = anomalies
return normal_patterns0x04 话术工程与心理操纵分析
社会工程学六大原则与攻击映射
Robert Cialdini 提出的六大影响力原则在社会工程学攻击中有明确的对应模式:
| Cialdini原则 | 攻击应用模式 | 典型话术示例 | 取证语言特征 |
|---|---|---|---|
| 互惠(Reciprocity) | 提供"免费"资源诱导回报 | “您的专属优惠”/“会员特权” | 过度承诺性语言 |
| 承诺与一致(Commitment) | 从小请求逐步升级 | “请先确认身份…现在需要验证码…” | 渐进式要求递增 |
| 权威(Authority) | 冒充高管/监管/IT | “CEO指示”/“合规部门要求” | 正式称谓+命令句式 |
| 社会证明(Social Proof) | 暗示他人已完成相同操作 | “其他同事都已更新”/“全公司都在使用” | 群体性表述 |
| 喜好(Liking) | 建立好感后再提要求 | 使用目标社交信息拉近距离 | 个性化引用+赞美语 |
| 稀缺(Scarcity) | 制造紧迫感和错失恐惧 | “24小时内失效”/“最后机会” | 时间限制+量化倒数 |
语言特征分析方法论
紧急性语言模式检测:
def analyze_urgency_patterns(text):
urgency_categories = {
'time_constraint': [
r'(?:立即|马上|即刻)[^,。.]*?(?:处理|操作|回复|确认)',
r'(?:within|in)\s+\d+\s+(?:minutes|hours|days)',
r'by\s+(?:EOD|COB|end of day|tomorrow)',
r' ASAP\b', r' urgent(?:ly)?\b', r' immediately\b'
],
'threat_of_loss': [
r'(?:账户|账号|account).*?(?:将被|will be).*?(?:锁定|关闭|suspend)',
r'(?:penalty|罚款|处罚)',
r'(?:legal action|法律诉讼)',
r'(?:permanent|永久).*?(?:damage|损害|loss)'
],
'authority_invocation': [
r'(?:CEO|CTO|CFO|总裁|总经理).*?(?:要求|指示|命令|request|order)',
r'(?:compliance|合规|regulatory|监管).*?(?:require|必须|需要)',
r'(?:法务|legal|lawyer|律师).*?(?:advice|建议|opinion|意见)'
],
'secrecy_demand': [
r'(?:保密|不要告诉|confidential|do not share)',
r'(?:between us|你我之间)',
r'(?:do not (?:disclose|announce|inform))',
r'(?:仅限|only for|restricted)'
]
}
results = {}
for category, patterns in urgency_categories.items():
matches = []
for pattern in patterns:
found = re.findall(pattern, text, re.IGNORECASE)
matches.extend(found)
results[category] = {
'count': len(matches),
'examples': matches[:5],
'severity': 'high' if len(matches) >= 3 else 'medium' if len(matches) >= 1 else 'low'
}
overall_score = sum(r['count'] for r in results.values())
results['overall_manipulation_score'] = min(overall_score / 10, 1.0)
return results翻译痕迹检测 — 多语言攻击识别:
当攻击者使用机器翻译生成中文钓鱼邮件时,常留下以下痕迹:
- 中式英语反向翻译的语法结构
- 不适用于中文语境的敬语/称谓混用
- 特定短语在不同段落间的一致性差异(分段翻译导致)
- 英文标点符号残留(如半角逗号在中文语境中)
- 直译导致的语义不通顺
def detect_translation_artifacts(text):
indicators = {
'mixed_scripts': bool(re.search(r'[\u4e00-\u9fff].*?[a-zA-Z]{3,}|[a-zA-Z]{3,}.*?[\u4e00-\u9fff]', text)),
'inconsistent_honorifics': bool(re.search(r'您.*你|你.*您', text)),
'english_punctuation': bool(re.search(r'[\u4e00-\u9fff]\s*[,;!?]\s*[\u4e00-\u9fff]', text)),
'calque_phrases': bool(re.search(r'根据我们的记录|per our records|按照我们的信息', text, re.IGNORECASE)),
'unnecessary_formality': bool(re.search(r'我们诚挚地通知您|we hereby inform you', text, re.IGNORECASE))
}
detected = sum(1 for v in indicators.values() if v)
return {
'artifacts_detected': detected,
'translation_likelihood': 'very_high' if detected >= 3 else 'high' if detected >= 2 else 'possible' if detected >= 1 else 'unlikely',
'details': indicators
}攻击者心理画像构建
基于话术分析的心理学建模方法:
ATTACKER_PROFILING_MODEL = {
"linguistic_background": {
"native_language": "基于翻译痕迹判断",
"education_level": "基于词汇复杂度和语法规范性",
"cultural_context": "基于节日引用、时间表达习惯、称谓方式"
},
"organizational_capability": {
"reconnaissance_depth": "基于对个人信息的掌握程度",
"template_quality": "基于视觉设计和文案质量",
"infrastructure_sophistication": "基于域名选择和服务器配置水平"
},
"psychological_techniques": {
"primary_leverage": "主要利用的心理原则",
"emotional_targets": ["恐惧", "好奇", "贪婪", "服从", "同情"],
"escalation_pattern": "从小要求到大要求的递进模式"
},
"operational_security": {
"anonymity_level": "身份信息保护措施",
"reusability": "基础设施复用程度",
"counter_forensics": "反取证意识强度"
}
}Vishing 与 Smishing 取证
Vishing 通话录音分析:
import speech_recognition as sr
from pydub import AudioSegment
def analyze_vishing_recording(audio_file_path):
audio = AudioSegment.from_file(audio_file_path)
duration = len(audio) / 1000
sample_rate = audio.frame_rate
channels = audio.channels
caller_profile = {
'duration_seconds': duration,
'accent_detection': analyze_accent(audio),
'background_noise': detect_background_environment(audio),
'voice_gender': detect_voice_gender(audio),
'estimated_age_range': estimate_age_from_voice(audio),
'language_dialect': detect_dialect(audio),
'voip_indicators': detect_voip_artifacts(audio),
'caller_id_spoof': None
}
recognizer = sr.Recognizer()
with sr.AudioFile(audio_file_path) as source:
audio_data = recognizer.record(source)
try:
transcript = recognizer.recognize_google(audio_data, language='zh-CN')
caller_profile['transcript'] = transcript
caller_profile['manipulation_analysis'] = analyze_urgency_patterns(transcript)
except sr.UnknownValueError:
caller_profile['transcript'] = 'Unable to transcribe'
return caller_profile
def detect_voip_artifacts(audio):
spectral_centroid = compute_spectral_centroid(audio)
codec_signatures = {
'g711': check_g711_codec(audio),
'g729': check_g729_codec(audio),
'opus': check_opus_codec(audio),
'amr': check_amr_codec(audio)
}
active_codecs = [k for k, v in codec_signatures.items() if v]
is_voip = len(active_codecs) > 0
jitter_level = measure_jitter(audio)
packet_loss_estimate = estimate_packet_loss(audio)
return {
'is_voip': is_voip,
'codec': active_codecs,
'jitter_ms': jitter_level,
'packet_loss_pct': packet_loss_estimate
}0x05 OSINT 调查技术
被动信息收集
域名/IP 情报平台对比:
| 平台 | 核心能力 | API限制 | 最佳用途 | 付费需求 |
|---|---|---|---|---|
| Shodan | 互联网设备扫描 | 100次/月(免费) | 开放端口/服务指纹 | 高级搜索需付费 |
| Censys | 全网TLS证书+主机 | 250次/月(免费) | 证书关联分析 | 批量查询需付费 |
| FOFA | 中国资产测绘 | 有限免费 | 国内目标分析 | 大量API需付费 |
| VirusTotal | 恶意文件/URL分析 | 4次/分钟(免费) | IOC验证 | Intelligence功能需付费 |
| URLscan.io | 网页截图+DOM分析 | 有限免费 | 钓鱼页面可视化 | 私有扫描需付费 |
| ThreatCrowd | 威胁情报聚合 | 无限制 | 多维度IOC关联 | 完全免费 |
Shodan 查询实战示例:
shodan search "http.html:'Microsoft Office' country:CN port:443" --fields ip_str,port,org,hostnames
shodan host 198.51.100.23
218.xxx.xxx.xxx
Hostnames: sender.campaign-server.io
Organization: XYZ Hosting LLC
OS: Linux
Ports: 22, 80, 443, 8080
shodan search "http.title:'Document Sharing Portal'" --fields ip_str,port,http.title证书透明度(Certificate Transparency)日志分析:
import requests
import json
def query_ct_logs(domain):
url = f"https://crt.sh/?q=%25.{domain}&output=json"
response = requests.get(url, timeout=30)
certificates = response.json()
cert_analysis = {}
for cert in certificates:
issuer = cert.get('issuer_name', '')
not_before = cert.get('not_before', '')
common_name = cert.get('common_name', '')
name_value = cert.get('name_value', '')
subdomains = name_value.split('\n')
for sd in subdomains:
sd_clean = sd.strip().lower()
if sd_clean not in cert_analysis:
cert_analysis[sd_clean] = []
cert_analysis[sd_clean].append({
'issuer': issuer,
'issued_date': not_before,
'cn': common_name
})
historical_subdomains = set()
for sd, certs in cert_analysis.items():
historical_subdomains.add(sd)
if len(certs) > 1:
print(f"[!] {sd} 有多张证书记录 → 可能频繁续期/重新申请")
suspicious_tlds = ['.xyz', '.top', '.tk', '.pw', '.cc', '.su']
for sd in historical_subdomains:
if any(sd.endswith(tld) for tld in suspicious_tlds):
print(f"[!] 可疑TLD子域名: {sd}")
return cert_analysis被动 DNS 分析:
使用 PassiveTotal / SecurityTrails / CirclPDNS 查询历史解析:
curl -s "https://api.passivetotal.org/v2/dns/passive" \
-u "$PT_USERNAME:$PT_API_KEY" \
-d '{"query": "198.51.100.23"}' | python3 -m json.tool
输出示例:
{
"results": [
{"resolve": "campaign-server.io", "firstSeen": "2025-12-01", "lastSeen": "2026-01-15", "recordType": "A"},
{"resolve": "micros0ft-security.com", "firstSeen": "2025-12-28", "lastSeen": "2026-01-20", "recordType": "A"},
{"resolve": "secure-login-verify.net", "firstSeen": "2026-01-05", "lastSeen": "2026-01-22", "recordType": "A"},
{"resolve": "office365-update.org", "firstSeen": "2026-01-10", "lastSeen": "2026-01-25", "recordType": "A"}
]
}
同一IP承载多个仿冒域名 → 确认为集中化钓鱼基础设施工具链整合
theHarvester 综合收集:
theHarvester -d company.com -b all -f output/company_recon.html
模块覆盖范围:
- baidu/bing/duckduckgo/yahoo: 搜索引擎枚举
- crtsh/certspotter: 证书透明度
- github-codesearch: GitHub代码搜索
- hackertarget/rapidDNS: 子域名暴力枚举
- linkedin/linkedin_links: LinkedIn人员收集
- twitter: Twitter账号发现
- urlscan: URL扫描历史
- vulners: 漏洞信息关联SpiderFoot 自动化 OSINT:
docker run -p 5001:5001 -v spiderfoot_data:/root/.spiderfoot spiderfoot/spiderfoot:latest
SpiderFoot 内置模块类别:
- DNS: 子域名枚举/反向DNS/区域传输
- Email: 邮箱泄露检查/PwnedDB查询
- Social: 社交媒体账号发现/用户名枚举
- Darknet: Tor市场搜索/暗网泄露监控
- Threat Intel: VirusTotal/Shodan/AbuseIPDB集成
- Geo: IP地理定位/GPS坐标解析Python 自动化 OSINT 框架
#!/usr/bin/env python3
import requests
import json
import dns.resolver
import socket
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from collections import defaultdict
class AutomatedOSINT:
def __init__(self, target_domain, api_keys=None):
self.target = target_domain
self.api_keys = api_keys or {}
self.results = defaultdict(dict)
self.ioc_set = set()
def passive_dns_lookup(self):
resolvers = [
('securitytrails', f"https://api.securitytrails.com/v1/history/{self.target}/dns/a"),
('circl_pdns', f"https://www.circl.lu/pdns/query/{self.target}")
]
for provider, url in resolvers:
try:
headers = {}
if provider == 'securitytrails':
headers['APIKEY'] = self.api_keys.get('securitytrails', '')
resp = requests.get(url, headers=headers, timeout=15)
if resp.status_code == 200:
self.results['passive_dns'][provider] = resp.json()
except Exception as e:
self.results['errors'].append(f'{provider}: {str(e)}')
def certificate_transparency_scan(self):
url = f"https://crt.sh/?q=%25.{self.target}&output=json"
try:
resp = requests.get(url, timeout=30)
certs = resp.json()
subdomains = set()
issuers = defaultdict(int)
for cert in certs:
names = cert.get('name_value', '').split('\n')
for name in names:
cleaned = name.strip().lower().lstrip('*.')
if cleaned and '.' in cleaned:
subdomains.add(cleaned)
issuer_cn = cert.get('issuer_ca_id', 'unknown')
issuers[str(issuer_cn)] += 1
self.results['cert_transparency'] = {
'subdomains': sorted(subdomains),
'certificate_count': len(certs),
'unique_issuers': dict(issuers),
'oldest_cert': min(c.get('not_before', '9999') for c in certs) if certs else None,
'newest_cert': max(c.get('not_before', '0000') for c in certs) if certs else None
}
except Exception as e:
self.results['errors'].append(f'crt.sh: {str(e)}')
def shodan_host_query(self, ip_address):
if 'shodan' not in self.api_keys:
return
url = f"https://api.shodan.io/shodan/host/{ip_address}?key={self.api_keys['shodan']}"
try:
resp = requests.get(url, timeout=15)
if resp.status_code == 200:
data = resp.json()
self.results['shodan'] = {
'ip': data.get('ip_str'),
'hostnames': data.get('hostnames', []),
'org': data.get('org', ''),
'os': data.get('os', ''),
'ports': data.get('ports', []),
'vulns': data.get('vulns', []),
'country': data.get('country_name', ''),
'city': data.get('city', '')
}
for hostname in data.get('hostnames', []):
self.ioc_set.add(hostname)
except Exception as e:
self.results['errors'].append(f'shodan: {str(e)}')
def subdomain_enum(self):
wordlist = ['mail', 'ftp', 'vpn', 'remote', 'webmail', 'admin', 'portal',
'api', 'dev', 'staging', 'test', 'owa', 'exchange', 'autodiscover',
'login', 'sso', 'auth', 'cloud', 'cdn', 'static', 'assets']
found = []
def resolve_subdomain(sub):
fqdn = f"{sub}.{self.target}"
try:
ip = socket.gethostbyname(fqdn)
return fqdn, ip
except socket.gaierror:
return fqdn, None
with ThreadPoolExecutor(max_workers=20) as executor:
futures = {executor.submit(resolve_subdomain, sub): sub for sub in wordlist}
for future in as_completed(futures):
fqdn, ip = future.result()
if ip:
found.append({'fqdn': fqdn, 'ip': ip})
self.ioc_set.add(ip)
self.results['subdomains'] = found
def generate_osint_report(self):
tasks = [
self.passive_dns_lookup,
self.certificate_transparency_scan,
self.subdomain_enum
]
threads = []
for task in tasks:
t = threading.Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join(timeout=60)
return {
'target': self.target,
'timestamp': __import__('datetime').datetime.utcnow().isoformat(),
'findings': dict(self.results),
'ioc_summary': {
'domains': [ioc for ioc in self.ioc_set if not ioc.replace('.','').isdigit()],
'ips': [ioc for ioc in self.ioc_set if ioc.replace('.','').isdigit()]
}
}0x06 社交媒体取证
平台取证方法论
Twitter/X 账号取证:
使用 snscrape 或 twint 提取公开推文及元数据:
snscrape --jsonl twitter-user @suspect_account > tweets.jsonl
Python 分析脚本:
import json
tweets = []
with open('tweets.jsonl', 'r') as f:
for line in f:
tweets.append(json.loads(line))
metadata_extraction = {
'account_created': tweets[0]['date'] if tweets else None,
'total_tweets': len(tweets),
'posting_schedule': analyze_posting_frequency(tweets),
'geotagged_posts': [t for t in tweets if t.get('coordinates')],
'mentioned_accounts': extract_mentions(tweets),
'hashtags_used': extract_hashtags(tweets),
'url_shorteners': extract_shortened_urls(tweets),
'image_metadata': extract_image_urls(tweets)
}图片地理定位技术(Geolocation):
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
import requests
def extract_gps_from_image(image_path):
image = Image.open(image_path)
exif_data = image._getexif()
if not exif_data:
return None
gps_info = {}
for tag_id, value in exif_data.items():
tag_name = TAGS.get(tag_id, tag_id)
if tag_name == 'GPSInfo':
for gps_tag_id, gps_value in value.items():
gps_tag_name = GPSTAGS.get(gps_tag_id, gps_tag_id)
gps_info[gps_tag_name] = gps_value
def convert_gps(value):
d = float(value[0])
m = float(value[1])
s = float(value[2])
return d + m/60.0 + s/3600.0
lat = convert_gps(gps_info['GPSLatitude'])
lon = convert_gps(gps_info['GPSLongitude'])
if gps_info['GPSLatitudeRef'] == 'S':
lat = -lat
if gps_info['GPSLongitudeRef'] == 'W':
lon = -lon
return {'latitude': lat, 'longitude': lon, 'altitude': gps_info.get('GPSAltitude')}
def reverse_geocode(lat, lon):
url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}"
headers = {'User-Agent': 'ForensicGeoLoc/1.0'}
resp = requests.get(url, headers=headers, timeout=10)
return resp.json().get('address', {})
def analyze_image_background(image_path):
from PIL import Image
img = Image.open(image_path)
width, height = img.size
regions = {
'sky': img.crop((0, 0, width, height//4)),
'mid_ground': img.crop((0, height//4, width, height//2)),
'foreground': img.crop((0, height//2, width, height))
}
dominant_colors = {}
for region_name, region_img in regions.items():
small = region_img.resize((50, 50))
pixels = list(small.getdata())
avg_color = tuple(sum(c)//len(pixels) for c in zip(*pixels))
dominant_colors[region_name] = avg_color
return dominant_colors社交关系图谱构建:
import networkx as nx
import matplotlib.pyplot as plt
def build_social_graph(accounts_data):
G = nx.DiGraph()
for account in accounts_data:
username = account['username']
G.add_node(username,
created=account['created_at'],
followers=account['followers_count'],
verified=account.get('verified', False))
for mention in account.get('mentions', []):
G.add_edge(username, mention['username'],
interaction_type=mention['type'],
frequency=mention['count'])
for follower in account.get('follows', []):
if not G.has_edge(username, follower):
G.add_edge(username, follower, interaction_type='follow')
centrality = nx.degree_centrality(G)
betweenness = nx.betweenness_centrality(G)
hubs = sorted(centrality.items(), key=lambda x: x[1], reverse=True)[:10]
bridges = sorted(betweenness.items(), key=lambda x: x[1], reverse=True)[:10]
return {
'graph': G,
'node_count': G.number_of_nodes(),
'edge_count': G.number_of_edges(),
'key_hubs': hubs,
'bridge_accounts': bridges,
'communities': nx.community.louvain_communities(G.to_undirected())
}即时通讯平台取证
Telegram 频道/群组取证:
import telethon
from telethon.sync import TelegramClient
async def telegram_channel_forensics(api_id, api_hash, channel_username):
async with TelegramClient('forensic_session', api_id, api_hash) as client:
entity = await client.get_entity(channel_username)
channel_info = {
'id': entity.id,
'title': entity.title,
'username': entity.username,
'date_created': entity.date,
'members_count': getattr(entity, 'participants_count', 'hidden'),
'description': getattr(entity, 'about', ''),
'linked_chat': getattr(entity, 'linked_chat_id', None)
}
messages = []
async for message in client.iter_messages(entity, limit=1000):
msg_data = {
'id': message.id,
'date': message.date.isoformat(),
'text': message.text,
'views': message.views,
'forwards': message.forwards,
'media': message.media is not None,
'edited': message.edit_date.isoformat() if message.edit_date else None,
'via_bot': message.via_bot.username if message.via_bot else None,
'fwd_from': message.fwd_from.from_id if message.fwd_from else None
}
messages.append(msg_data)
admins = []
try:
async for admin in client.iter_participants(entity, filter=telethon.tl.types.ChannelParticipantsAdmins):
admins.append({
'id': admin.id,
'username': admin.username,
'role': admin.participant_rank
})
except Exception:
pass
return {
'channel_info': channel_info,
'messages': messages,
'admins': admins,
'message_frequency': analyze_message_frequency(messages)
}Discord 服务器取证:
import discord
import asyncio
from datetime import datetime
class DiscordForensicBot(discord.Client):
def __init__(self, guild_id):
intents = discord.Intents.all()
super().__init__(intents=intents)
self.guild_id = guild_id
self.forensic_data = {
'guild_info': None,
'channels': {},
'members': [],
'roles': [],
'audit_log': []
}
async def collect_guild_forensics(self):
guild = self.get_guild(self.guild_id)
self.forensic_data['guild_info'] = {
'name': guild.name,
'id': guild.id,
'owner_id': guild.owner_id,
'created_at': guild.created_at.isoformat(),
'member_count': guild.member_count,
'boost_level': guild.premium_tier,
'verification_level': str(guild.verification_level),
'features': guild.features
}
for role in guild.roles:
self.forensic_data['roles'].append({
'name': role.name,
'id': role.id,
'permissions': list(role.permissions),
'member_count': len(role.members),
'color': str(role.color),
'hoisted': role.hoist,
'mentionable': role.mentionable,
'created_at': role.created_at.isoformat()
})
async for entry in guild.audit_logs(limit=500):
self.forensic_data['audit_log'].append({
'action': str(entry.action),
'user': str(entry.user),
'target': str(entry.target),
'reason': entry.reason,
'created_at': entry.created_at.isoformat(),
'changes': str(entry.changes.before) + ' -> ' + str(entry.changes.after)
})
await self.close()0x07 深度伪造检测
Deepfake 技术分类与检测方法
图像深度伪造检测流水线:
import cv2
import numpy as np
from PIL import Image
class DeepfakeDetector:
def error_level_analysis(self, image_path, quality=95):
img = cv2.imread(image_path)
resized = cv2.resize(img, (512, 512))
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
_, compressed = cv2.imencode('.jpg', resized, encode_param)
decompressed = cv2.imdecode(compressed, cv2.IMREAD_UNCHANGED)
ela = cv2.absdiff(resized, decompressed)
ela_normalized = cv2.normalize(ela, None, 0, 255, cv2.NORM_MINMAX)
mean_error = np.mean(ela)
std_error = np.std(ela)
regions = self.segment_into_grid(ela_normalized, grid_size=(4, 4))
anomaly_regions = []
global_mean = np.mean(ela_normalized)
global_std = np.std(ela_normalized)
for idx, region in enumerate(regions):
region_mean = np.mean(region)
z_score = (region_mean - global_mean) / (global_std + 1e-6)
if abs(z_score) > 2.0:
anomaly_regions.append({
'region_index': idx,
'mean_intensity': float(region_mean),
'z_score': float(z_score),
'anomaly_type': 'likely_manipulated' if z_score > 0 else 'likely_original'
})
return {
'mean_ela_error': float(mean_error),
'std_ela_error': float(std_error),
'anomaly_regions': anomaly_regions,
'manipulation_probability': min(len(anomaly_regions) / 16.0, 1.0)
}
def frequency_domain_analysis(self, image_path):
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
img_float = np.float32(img)
dft = cv2.dft(img_float)
magnitude_spectrum = cv2.magnitude(dft[:, :, 0], dft[:, :, 1])
magnitude_log = np.log1p(magnitude_spectrum)
h, w = magnitude_log.shape
center_y, center_x = h // 2, w // 2
low_freq_mask = np.zeros_like(magnitude_log)
cv2.circle(low_freq_mask, (center_x, center_y), min(h, w) // 8, 1, -1)
high_freq_energy = np.sum(magnitude_log * (1 - low_freq_mask))
total_energy = np.sum(magnitude_log)
high_freq_ratio = high_freq_energy / (total_energy + 1e-6)
artifacts = self.detect_periodic_artifacts(magnitude_log)
return {
'high_frequency_ratio': float(high_freq_ratio),
'periodic_artifacts_detected': artifacts['detected'],
'artifact_frequencies': artifacts.get('frequencies', []),
'synthetic_likelihood': 'high' if high_freq_ratio > 0.6 or artifacts['detected'] else 'low'
}
def facial_consistency_check(self, image_path):
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 4)
consistency_results = {}
if len(faces) > 0:
x, y, w, h = faces[0]
face_roi = gray[y:y+h, x:x+w]
left_eye_region = face_roi[0:h//3, 0:w//2]
right_eye_region = face_roi[0:h//3, w//2:]
left_eye_hist = cv2.calcHist([left_eye_region], [0], None, [256], [0, 256])
right_eye_hist = cv2.calcHist([right_eye_region], [0], None, [256], [0, 256])
eye_symmetry = cv2.compareHist(left_eye_hist, right_eye_hist, cv2.HISTCMP_CORREL)
consistency_results['eye_symmetry'] = float(eye_symmetry)
consistency_results['face_aspect_ratio'] = w / h if h > 0 else 0
consistency_results['face_count'] = len(faces)
skin_tone_regions = [
face_roi[h//3:2*h//3, w//4:3*w//4],
face_roi[2*h//3:, w//4:3*w//4]
]
skin_colors = [np.mean(r) for r in skin_tone_regions if r.size > 0]
if len(skin_colors) >= 2:
consistency_results['skin_tone_variance'] = float(np.std(skin_colors))
return consistency_results音频深度伪造检测:
import librosa
import numpy as np
from scipy import signal
class AudioDeepfakeDetector:
def __init__(self, audio_path):
self.audio_path = audio_path
self.y, self.sr = librosa.load(audio_path, sr=22050)
def spectral_analysis(self):
spectral_centroid = librosa.feature.spectral_centroid(y=self.y, sr=self.sr)
spectral_rolloff = librosa.feature.spectral_rolloff(y=self.y, sr=self.sr)
spectral_bandwidth = librosa.feature.spectral_bandwidth(y=self.y, sr=self.sr)
zero_crossing_rate = librosa.feature.zero_crossing_rate(self.y)
centroid_mean = float(np.mean(spectral_centroid))
rolloff_mean = float(np.mean(spectral_rolloff))
bandwidth_mean = float(np.mean(spectral_bandwidth))
synthetic_indicators = {
'abnormally_smooth_spectrum': float(np.std(spectral_centroid)) < 100,
'missing_high_frequency_detail': rolloff_mean < 4000,
'unnatural_bandwidth': bandwidth_mean < 500,
'zero_crossing_anomaly': float(np.std(zero_crossing_rate)) < 0.01
}
return {
'spectral_centroid_mean': centroid_mean,
'spectral_rolloff_mean': rolloff_mean,
'spectral_bandwidth_mean': bandwidth_mean,
'synthetic_likelihood': sum(1 for v in synthetic_indicators.values() if v),
'indicators': synthetic_indicators
}
def breathing_pattern_analysis(self):
hop_length = 512
energy = librosa.feature.rms(y=self.y, hop_length=hop_length)[0]
silent_frames = energy < np.percentile(energy, 10)
breathing_candidates = []
in_breath = False
breath_start = 0
for i, is_silent in enumerate(silent_frames):
if is_silent and not in_breath:
breath_start = i
in_breath = True
elif not is_silent and in_breath:
breath_duration_frames = i - breath_start
breath_duration_sec = breath_duration_frames * hop_length / self.sr
if 0.1 < breath_duration_sec < 3.0:
breathing_candidates.append({
'frame': breath_start,
'duration_seconds': breath_duration_sec,
'time_seconds': breath_start * hop_length / self.sr
})
in_breath = False
regular_intervals = []
for i in range(1, len(breathing_candidates)):
interval = breathing_candidates[i]['time_seconds'] - breathing_candidates[i-1]['time_seconds']
regular_intervals.append(interval)
interval_regularity = np.std(regular_intervals) if regular_intervals else float('inf')
return {
'breathing_events_found': len(breathing_candidates),
'average_interval': float(np.mean(regular_intervals)) if regular_intervals else None,
'interval_regularity_std': float(interval_regularity),
'too_regular': interval_regularity < 0.3 if regular_intervals else False
}
def environmental_noise_consistency(self):
noise_frames = librosa.effects.split(self.y, top_db=30)
if len(noise_frames) < 2:
return {'consistent': True, 'note': 'insufficient_noise_segments'}
noise_spectra = []
for start, end in noise_frames:
segment = self.y[start:end]
if len(segment) > 0:
spectrum = np.abs(np.fft.rfft(segment))
noise_spectra.append(spectrum)
correlations = []
for i in range(len(noise_spectra)):
for j in range(i+1, len(noise_spectra)):
min_len = min(len(noise_spectra[i]), len(noise_spectra[j]))
corr = np.corrcoef(noise_spectra[i][:min_len], noise_spectra[j][:min_len])[0, 1]
correlations.append(corr)
avg_correlation = float(np.mean(correlations)) if correlations else 0
return {
'noise_segments': len(noise_frames),
'average_noise_correlation': avg_correlation,
'consistent_environment': avg_correlation > 0.8,
'possible_splicing': avg_correlation < 0.3 and len(noise_frames) > 3
}Deepfake 检测工具对比:
| 工具/平台 | 检测类型 | 准确率 | 部署方式 | 适用场景 |
|---|---|---|---|---|
| Deepware Scanner | 视频换脸 | 92%+ | Web API | 快速视频验证 |
| Hive Moderation | 图像/视频 AI生成 | 95%+ | API | 多模态内容审核 |
| Microsoft Video Authenticator | 视频深度伪造 | 90%+ | SDK | 企业级集成 |
| Sensity AI | 多类型深度伪造 | 93%+ | API+Dashboard | 持续监控 |
| Intel FakeCatcher | 视频真实性 | 96%+ | SDK | 实时检测 |
| GetReal | 面部操纵检测 | 91%+ | Web | 快速筛查 |
0x08 物理社会工程学取证
物理入侵取证方法
监控录像分析取证:
物理社会工程学取证的核心在于将物理世界的证据与数字世界的证据进行关联。监控录像分析是首要步骤:
import cv2
import os
from datetime import datetime
class PhysicalSEForensics:
def __init__(self, cctv_directory, access_log_path):
self.cctv_dir = cctv_directory
self.access_logs = self.parse_access_logs(access_log_path)
def parse_access_logs(self, log_path):
logs = []
with open(log_path, 'r') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 4:
logs.append({
'timestamp': datetime.strptime(parts[0], '%Y-%m-%d %H:%M:%S'),
'badge_id': parts[1],
'door_id': parts[2],
'access_result': parts[3],
'photo_ref': parts[4] if len(parts) > 4 else None
})
return logs
def detect_tailgating(self, video_path, fps=30):
cap = cv2.VideoCapture(video_path)
frame_count = 0
person_detections = []
person_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_fullbody.xml')
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame_count += 1
if frame_count % fps == 0:
persons = person_cascade.detectMultiScale(frame, 1.1, 3)
if len(persons) > 1:
badge_time = self.find_closest_access_event(frame_count / fps)
if badge_time:
person_detections.append({
'timestamp_seconds': frame_count / fps,
'person_count': len(persons),
'badge_swipe_count': 1,
'tailgating_suspected': len(persons) > 1,
'closest_badge_event': badge_time
})
cap.release()
return person_detections
def find_closest_access_event(self, video_timestamp):
video_dt = self.base_time + __import__('datetime').timedelta(seconds=video_timestamp)
closest = None
min_diff = float('inf')
for log in self.access_logs:
diff = abs((log['timestamp'] - video_dt).total_seconds())
if diff < min_diff:
min_diff = diff
closest = log
return closest if min_diff < 5 else None
def correlate_physical_digital(self, suspicious_events):
correlations = []
for event in suspicious_events:
time_window = __import__('datetime').timedelta(minutes=5)
event_time = event.get('timestamp')
matching_digital = []
for log in self.access_logs:
if abs((log['timestamp'] - event_time).total_seconds()) <= time_window.total_seconds():
matching_digital.append(log)
correlations.append({
'physical_event': event,
'matching_access_logs': matching_digital,
'badge_used': event.get('badge_id'),
'anomaly': len(matching_digital) > 1 or event.get('tailgating_suspected')
})
return correlations设备投放取证
USB 投放设备分析:
USB 设备取证检查流程:
1. 物理检查:
- 记录设备外观、品牌、型号、序列号
- 拍照固定证据
- 检查是否有物理损坏或改装痕迹
2. 写保护后连接取证工作站:
dmesg | tail -20
lsusb -v
fdisk -l /dev/sdX
3. 创建磁盘镜像:
dd if=/dev/sdX of=/evidence/usb_image.dd bs=4M status=progress
sha256sum /evidence/usb_image.dd > /evidence/usb_image.dd.sha256
4. 挂载分析:
mount -o ro /evidence/usb_image.dd /mnt/usb_analysis
find /mnt/usb_analysis -type f -exec file {} \;
find /mnt/usb_analysis -type f -exec md5sum {} \;
5. 检查恶意配置:
- autorun.inf / autorun.sh 自动执行文件
- 伪装的固件更新文件
- BadUSB HID 注入脚本
- 隐藏的分区或数据区恶意充电站检测:
import usb.core
import usb.util
def detect_malicious_charging_station():
devices = usb.core.find(find_all=True)
suspicious_devices = []
for dev in devices:
device_info = {
'vendor_id': hex(dev.idVendor),
'product_id': hex(dev.idProduct),
'manufacturer': usb.util.get_string(dev, dev.iManufacturer) if dev.iManufacturer else 'unknown',
'product': usb.util.get_string(dev, dev.iProduct) if dev.iProduct else 'unknown',
'serial': usb.util.get_string(dev, dev.iSerialNumber) if dev.iSerialNumber else 'unknown',
'device_class': dev.bDeviceClass,
'interfaces': []
}
for cfg in dev:
for intf in cfg:
device_info['interfaces'].append({
'class': intf.bInterfaceClass,
'subclass': intf.bInterfaceSubClass,
'protocol': intf.bInterfaceProtocol
})
is_data_device = any(
intf['class'] in [0x08, 0x03, 0x02]
for intf in device_info['interfaces']
)
is_known_charger = device_info['device_class'] == 0x00 and len(device_info['interfaces']) == 0
if is_data_device:
device_info['risk'] = 'HIGH - USB charging port with data capabilities detected'
suspicious_devices.append(device_info)
elif not is_known_charger:
device_info['risk'] = 'MEDIUM - Unusual device characteristics'
suspicious_devices.append(device_info)
return suspicious_devices物理证据保全与数字证据关联
物理社工取证的独特挑战在于物理证据与数字证据的时空关联。以下框架用于系统化地建立这种关联:
| 物理证据类型 | 采集方法 | 关联数字证据 | 时间关联窗口 |
|---|---|---|---|
| 门禁刷卡记录 | 门禁系统导出 | VPN登录/网络接入日志 | ±5分钟 |
| 监控录像 | DVR/NVR提取 | 终端登录事件/文件访问日志 | ±10分钟 |
| USB设备投放 | 物理扣押+镜像 | USB连接事件/驱动安装日志 | ±30分钟 |
| 伪装身份访客记录 | 前台登记簿 | 内部系统账号创建/权限变更 | ±24小时 |
| 无线接入点 | 设备扣押 | WLC日志/DHCP分配记录 | ±1小时 |
| 物理文件/便签 | 拍照+扣押 | 对应数字文档/密码使用记录 | 无固定窗口 |
0x09 内部威胁关联分析
内部威胁与社会工程学关联模型
社会工程学攻击的最终目标往往是建立内部协助者(insider),无论是被欺骗的无意内部人员还是被策反的恶意内部人员。内部威胁取证需要建立以下关联模型:
员工行为异常检测框架:
import numpy as np
from datetime import datetime, timedelta
class InsiderThreatDetector:
def __init__(self, user_activity_logs, baseline_days=90):
self.logs = user_activity_logs
self.baseline_days = baseline_days
def detect_access_anomalies(self, user_id):
user_logs = [l for l in self.logs if l['user_id'] == user_id]
baseline_logs = [l for l in user_logs
if (datetime.now() - l['timestamp']).days > self.baseline_days]
recent_logs = [l for l in user_logs
if (datetime.now() - l['timestamp']).days <= self.baseline_days]
baseline_resources = set(l['resource_accessed'] for l in baseline_logs)
recent_resources = set(l['resource_accessed'] for l in recent_logs)
new_resources = recent_resources - baseline_resources
sensitive_keywords = ['password', 'credential', 'secret', 'key', 'token',
'financial', 'hr', 'salary', 'customer_data', 'pii']
sensitive_new_access = []
for resource in new_resources:
if any(kw in resource.lower() for kw in sensitive_keywords):
sensitive_new_access.append(resource)
baseline_hours = {}
for log in baseline_logs:
hour = log['timestamp'].hour
baseline_hours.setdefault(hour, 0)
baseline_hours[hour] += 1
recent_hours = {}
for log in recent_logs:
hour = log['timestamp'].hour
recent_hours.setdefault(hour, 0)
recent_hours[hour] += 1
off_hours_baseline = sum(baseline_hours.get(h, 0) for h in [0,1,2,3,4,5,23])
off_hours_recent = sum(recent_hours.get(h, 0) for h in [0,1,2,3,4,5,23])
return {
'new_sensitive_resources': sensitive_new_access,
'off_hours_baseline': off_hours_baseline,
'off_hours_recent': off_hours_recent,
'off_hours_spike': off_hours_recent > off_hours_baseline * 2,
'total_new_resources': len(new_resources)
}
def detect_data_exfiltration(self, user_id):
user_logs = [l for l in self.logs if l['user_id'] == user_id]
large_transfers = [l for l in user_logs
if l.get('transfer_size_mb', 0) > 100]
external_uploads = [l for l in user_logs
if l.get('destination_type') == 'external'
and l.get('action') in ['upload', 'send', 'share']]
archive_creations = [l for l in user_logs
if l.get('action') in ['compress', 'zip', 'encrypt']
and l.get('file_count', 0) > 10]
usb_copies = [l for l in user_logs
if l.get('destination_type') == 'usb'
and l.get('action') == 'copy']
printing_events = [l for l in user_logs
if l.get('action') == 'print'
and l.get('page_count', 0) > 50]
exfil_indicators = {
'large_transfers': len(large_transfers),
'external_uploads': len(external_uploads),
'archive_creations': len(archive_creations),
'usb_copies': len(usb_copies),
'bulk_printing': len(printing_events),
'total_indicators': len(large_transfers) + len(external_uploads) +
len(archive_creations) + len(usb_copies) + len(printing_events)
}
return exfil_indicators
def detect_social_engineering_susceptibility(self, user_id):
user_logs = [l for l in self.logs if l['user_id'] == user_id]
phishing_clicks = [l for l in user_logs
if l.get('event_type') == 'phishing_link_clicked']
password_resets = [l for l in user_logs
if l.get('event_type') == 'password_reset'
and l.get('trigger') == 'user_request']
unusual_grants = [l for l in user_logs
if l.get('event_type') == 'permission_grant'
and l.get('authorized_by') != 'admin']
return {
'phishing_click_count': len(phishing_clicks),
'self_service_resets': len(password_resets),
'unauthorized_grants': len(unusual_grants),
'susceptibility_score': min((len(phishing_clicks) * 3 +
len(password_resets) * 2 +
len(unusual_grants) * 5) / 20, 1.0)
}UEBA 应用与 Sigma 规则
内部威胁检测 Sigma 规则集:
title: Suspicious Off-Hours Access to Sensitive Resources
id: a1b2c3d4-5678-90ab-cdef-111111111111
status: experimental
description: 检测非工作时间对敏感资源的异常访问行为
author: SOC Team
date: 2026/07/02
logsource:
category: process_creation
product: windows
detection:
selection:
ParentImage|endswith:
- '\powershell.exe'
- '\cmd.exe'
- '\wscript.exe'
- '\cscript.exe'
CommandLine|contains:
- 'password'
- 'credential'
- 'export'
- 'download'
- 'compress'
timeframe:
field: Timestamp
condition:
lt: '06:00:00'
gt: '22:00:00'
condition: selection and timeframe
level: high
tags:
- attack.exfiltration
- attack.credential_access
- insider_threattitle: Unusual Volume of File Access by Single User
id: a1b2c3d4-5678-90ab-cdef-222222222222
status: experimental
description: 检测单用户在短时间内访问大量文件的行为
author: SOC Team
date: 2026/07/02
logsource:
category: file_event
product: windows
detection:
selection:
TargetFilename|contains:
- '\\Finance\\'
- '\\HR\\'
- '\\Legal\\'
- '\\Confidential\\'
condition: selection | count(TargetFilename) by User > 100
timeframe: 1h
level: medium
tags:
- attack.collection
- insider_threat离职员工风险评估
def assess_departing_employee_risk(employee_id, departure_type, access_history):
risk_factors = {
'departure_type': {
'voluntary': 3,
'involuntary': 7,
'retirement': 1,
'termination_for_cause': 9
},
'access_level': {
'standard': 2,
'elevated': 5,
'admin': 8,
'domain_admin': 10
},
'data_sensitivity': {
'general': 1,
'confidential': 5,
'restricted': 8,
'top_secret': 10
},
'recent_behavior': {
'normal': 0,
'policy_violations': 4,
'access_anomalies': 6,
'data_exfil_indicators': 9
}
}
score = 0
score += risk_factors['departure_type'].get(departure_type, 5)
max_access = max(access_history, key=lambda x: x.get('access_level_score', 0))
score += risk_factors['access_level'].get(max_access.get('access_level', 'standard'), 2)
if access_history[-30:]:
recent_anomalies = [a for a in access_history[-30:] if a.get('anomaly')]
score += min(len(recent_anomalies) * 2, 10)
total_score = min(score / 30, 1.0)
return {
'employee_id': employee_id,
'risk_score': total_score,
'risk_level': 'critical' if total_score > 0.8 else 'high' if total_score > 0.6 else 'medium' if total_score > 0.3 else 'low',
'recommended_actions': get_recommended_actions(total_score)
}
def get_recommended_actions(risk_score):
if risk_score > 0.8:
return ['immediate_access_revocation', 'escort_from_premises', 'device_confiscation', 'full_audit_log_review']
elif risk_score > 0.6:
return ['access_revocation_within_24h', 'manager_notification', 'data_access_audit']
elif risk_score > 0.3:
return ['standard_offboarding', 'knowledge_transfer_session', 'monitoring_during_notice_period']
else:
return ['standard_offboarding_procedure']0x0A 证据强度分层与关联分析
证据强度分类体系
社会工程学取证中的证据需要按可靠性进行分层,以便在报告和法律诉讼中准确表述:
| 证据层级 | 定义 | 示例 | 法律效力 | 报告标注 |
|---|---|---|---|---|
| L1-确认恶意 | 可直接证明恶意意图和行为 | 钓鱼邮件源码、攻击者自白、C2通信日志 | 直接证据 | 🔴 CONFIRMED MALICIOUS |
| L2-高度可疑 | 强烈暗示恶意但需佐证的间接证据 | 域名注册信息异常、基础设施关联、话术模式匹配 | 间接证据/情况证据 | 🟠 HIGHLY SUSPICIOUS |
| L3-需要关注 | 存在异常但无法直接归因的弱信号 | 异常登录时间、新设备指纹、非典型通信模式 | 需进一步调查 | 🟡 REQUIRES ATTENTION |
| L4-信息性 | 提供上下文但无直接证明价值 | WHOIS隐私保护、通用基础设施、公开工具使用 | 背景信息 | 🔵 INFORMATIONAL |
多源证据关联方法
from collections import defaultdict
from datetime import datetime
class EvidenceCorrelator:
def __init__(self):
self.evidence_items = []
self.correlation_graph = defaultdict(set)
def add_evidence(self, evidence_type, source, indicator, timestamp, confidence_level):
item = {
'id': len(self.evidence_items),
'type': evidence_type,
'source': source,
'indicator': indicator,
'timestamp': timestamp,
'confidence': confidence_level,
'correlated_with': set()
}
self.evidence_items.append(item)
return item['id']
def correlate_by_indicator(self):
indicator_map = defaultdict(list)
for item in self.evidence_items:
indicator_map[item['indicator']].append(item['id'])
for indicator, ids in indicator_map.items():
if len(ids) > 1:
for i in range(len(ids)):
for j in range(i+1, len(ids)):
self.evidence_items[ids[i]]['correlated_with'].add(ids[j])
self.evidence_items[ids[j]]['correlated_with'].add(ids[i])
self.correlation_graph[ids[i]].add(ids[j])
self.correlation_graph[ids[j]].add(ids[i])
def correlate_by_time_window(self, window_minutes=60):
sorted_items = sorted(self.evidence_items, key=lambda x: x['timestamp'])
for i in range(len(sorted_items)):
for j in range(i+1, len(sorted_items)):
time_diff = (sorted_items[j]['timestamp'] - sorted_items[i]['timestamp']).total_seconds()
if time_diff > window_minutes * 60:
break
if sorted_items[i]['source'] != sorted_items[j]['source']:
self.evidence_items[sorted_items[i]['id']]['correlated_with'].add(sorted_items[j]['id'])
self.evidence_items[sorted_items[j]['id']]['correlated_with'].add(sorted_items[i]['id'])
def generate_attribution_assessment(self):
clusters = self._find_connected_components()
assessments = []
for cluster in clusters:
cluster_items = [self.evidence_items[i] for i in cluster]
l1_count = sum(1 for i in cluster_items if i['confidence'] == 'L1')
l2_count = sum(1 for i in cluster_items if i['confidence'] == 'L2')
if l1_count >= 2:
attribution_confidence = 'HIGH'
elif l1_count >= 1 and l2_count >= 3:
attribution_confidence = 'MEDIUM-HIGH'
elif l2_count >= 3:
attribution_confidence = 'MEDIUM'
else:
attribution_confidence = 'LOW'
assessments.append({
'cluster_size': len(cluster),
'evidence_types': list(set(i['type'] for i in cluster_items)),
'l1_evidence_count': l1_count,
'l2_evidence_count': l2_count,
'attribution_confidence': attribution_confidence,
'indicators': list(set(i['indicator'] for i in cluster_items))
})
return assessments
def _find_connected_components(self):
visited = set()
components = []
def dfs(node, component):
visited.add(node)
component.append(node)
for neighbor in self.correlation_graph.get(node, set()):
if neighbor not in visited:
dfs(neighbor, component)
for i in range(len(self.evidence_items)):
if i not in visited:
component = []
dfs(i, component)
if len(component) > 1:
components.append(component)
return componentsIOC 汇总与 STIX/TAXII 共享格式
import json
from datetime import datetime
def generate_stix_bundle(iocs, attack_description):
stix_objects = []
bundle_id = f"bundle--{__import__('uuid').uuid4()}"
indicator_objects = []
for ioc in iocs:
indicator = {
'type': 'indicator',
'spec_version': '2.1',
'id': f"indicator--{__import__('uuid').uuid4()}",
'created': datetime.utcnow().isoformat() + 'Z',
'modified': datetime.utcnow().isoformat() + 'Z',
'name': ioc.get('name', 'SE Attack IOC'),
'description': ioc.get('description', ''),
'pattern': ioc.get('stix_pattern', ''),
'pattern_type': 'stix',
'valid_from': datetime.utcnow().isoformat() + 'Z',
'labels': ioc.get('labels', ['malicious-activity']),
'confidence': ioc.get('confidence', 50)
}
indicator_objects.append(indicator)
stix_objects.append(indicator)
attack_pattern = {
'type': 'attack-pattern',
'spec_version': '2.1',
'id': f"attack-pattern--{__import__('uuid').uuid4()}",
'created': datetime.utcnow().isoformat() + 'Z',
'name': 'Social Engineering Attack',
'description': attack_description,
'external_references': [
{
'source_name': 'mitre-attack',
'external_id': 'T1566',
'url': 'https://attack.mitre.org/techniques/T1566/'
}
]
}
stix_objects.append(attack_pattern)
bundle = {
'type': 'bundle',
'id': bundle_id,
'objects': stix_objects
}
return bundle0x0B 自动化检测与防御
邮件网关检测规则
SpamAssassin 自定义规则集:
/etc/spamassassin/local.cf 追加规则:
header SE_URGENCY_LANGUAGE Subject =~ /(?:urgent|immediate|immediate action|required now|expiring soon)/i
score SE_URGENCY_LANGUAGE 2.0
describe SE_URGENCY_LANGUAGE Subject contains urgency language common in SE attacks
header SE_DISPLAY_URL_MISMATCH eval:check_display_url_mismatch()
score SE_DISPLAY_URL_MISMATCH 3.5
describe SE_DISPLAY_URL_MISMATCH Display URL differs from actual link destination
header SE_NEW_DOMAIN eval:check_sender_domain_age()
score SE_NEW_DOMAIN 2.5
describe SE_NEW_DOMAIN Sender domain registered within last 30 days
header SE_HOMOGYPH_DOMAIN eval:check_homoglyph_domain()
score SE_HOMOGYPH_DOMAIN 4.0
describe SE_HOMOGYPH_DOMAIN Sender domain uses homoglyph characters
header SE_REPLY_MISMATCH eval:check_reply_to_from_mismatch()
score SE_REPLY_MISMATCH 1.5
describe SE_REPLY_MISMATCH Reply-To differs from From address
header SE_SECRECY_LANGUAGE Body =~ /(?:do not (?:tell|share|disclose)|confidential|between us|keep this quiet)/i
score SE_SECRECY_LANGUAGE 2.0
describe SE_SECRECY_LANGUAGE Body contains secrecy demands
header SE_MX_MAILGUN Received =~ /mailgun\.org|sendgrid\.net|amazonses\.com/i
score SE_MX_MAILGUN 0.5
describe SE_MX_MAILGUN Email sent via transactional email service
header SE_NO_DMARC_REJECT eval:check_dmarc_policy_none()
score SE_NO_DMARC_REJECT 1.0
describe SE_NO_DMARC_REJECT Sender domain has DMARC p=none policyExchange Online Transport Rules:
New-TransportRule -Name "SE Detection - Urgency + External" `
-FromScope "NotInOrganization" `
-SubjectOrBodyContainsWords @("urgent","immediately","ASAP","within 24 hours","account will be closed","immediate verification") `
-SubjectOrBodyContainsWords @("click here","verify now","confirm identity","update information") `
-SetSCL 7 `
-PrependSubject "[SE ALERT] " `
-GenerateIncidentReport "soc@company.com" `
-IncidentReportContent "OriginalMail,Headers"
New-TransportRule -Name "SE Detection - New Domain + Financial Keywords" `
-FromScope "NotInOrganization" `
-SenderDomainIs (Get-NewlyRegisteredDomains) `
-SubjectOrBodyContainsWords @("wire transfer","bank account","payment","invoice","update bank","change account") `
-SetSCL 9 `
-RedirectMessageTo "finance-security@company.com" `
-GenerateIncidentReport "soc@company.com"
New-TransportRule -Name "SE Detection - Display Name Spoofing" `
-FromScope "NotInOrganization" `
-SenderAddressMatchesPatterns @(Get-ExecNames) `
-SetSCL 6 `
-PrependSubject "[SPOOF ALERT] " `
-GenerateIncidentReport "soc@company.com"域名相似度检测算法
import math
from collections import Counter
def levenshtein_distance(s1, s2):
if len(s1) < len(s2):
return levenshtein_distance(s2, s1)
if len(s2) == 0:
return len(s1)
previous_row = range(len(s2) + 1)
for i, c1 in enumerate(s1):
current_row = [i + 1]
for j, c2 in enumerate(s2):
insertions = previous_row[j + 1] + 1
deletions = current_row[j] + 1
substitutions = previous_row[j] + (c1 != c2)
current_row.append(min(insertions, deletions, substitutions))
previous_row = current_row
return previous_row[-1]
def jaro_winkler_similarity(s1, s2):
if s1 == s2:
return 1.0
len1, len2 = len(s1), len(s2)
if len1 == 0 or len2 == 0:
return 0.0
match_distance = max(len1, len2) // 2 - 1
if match_distance < 0:
match_distance = 0
s1_matches = [False] * len1
s2_matches = [False] * len2
matches = 0
transpositions = 0
for i in range(len1):
start = max(0, i - match_distance)
end = min(i + match_distance + 1, len2)
for j in range(start, end):
if s2_matches[j] or s1[i] != s2[j]:
continue
s1_matches[i] = True
s2_matches[j] = True
matches += 1
break
if matches == 0:
return 0.0
k = 0
for i in range(len1):
if not s1_matches[i]:
continue
while not s2_matches[k]:
k += 1
if s1[i] != s2[k]:
transpositions += 1
k += 1
jaro = (matches / len1 + matches / len2 + (matches - transpositions / 2) / matches) / 3
prefix = 0
for i in range(min(len1, len2, 4)):
if s1[i] == s2[i]:
prefix += 1
else:
break
winkler = jaro + prefix * 0.1 * (1 - jaro)
return winkler
def detect_suspicious_domains(target_domain, observed_domains):
target_name = target_domain.split('.')[0]
target_tld = '.'.join(target_domain.split('.')[1:])
results = []
for domain in observed_domains:
obs_name = domain.split('.')[0]
obs_tld = '.'.join(domain.split('.')[1:])
edit_dist = levenshtein_distance(target_name, obs_name)
jw_sim = jaro_winkler_similarity(target_name, obs_name)
homoglyph_chars = sum(1 for a, b in zip(target_name, obs_name) if a != b and ord(a) != ord(b))
risk_score = 0
if edit_dist <= 2:
risk_score += 40
if jw_sim > 0.85:
risk_score += 30
if homoglyph_chars > 0:
risk_score += 20
if obs_tld != target_tld:
risk_score += 10
results.append({
'domain': domain,
'edit_distance': edit_dist,
'jaro_winkler': jw_sim,
'homoglyph_chars': homoglyph_chars,
'risk_score': min(risk_score, 100),
'classification': 'confirmed_squat' if risk_score >= 70 else 'likely_squat' if risk_score >= 40 else 'monitor'
})
return sorted(results, key=lambda x: x['risk_score'], reverse=True)SOAR 集成方案
playbook: SE_Incident_Response
trigger:
- condition: email_alert
- condition: user_report
steps:
step_1_extract_iocs:
action: extract_email_headers
input: "{{trigger.raw_email}}"
output: ioc_list
step_2_enrich_domains:
action: domain_enrichment
input: "{{step_1_extract_iocs.domains}}"
tools:
- whois_lookup
- dns_resolution
- vt_query
- urlscan_submit
output: enriched_domains
step_3_check_reputation:
action: reputation_check
input: "{{step_2_enrich_domains}}"
sources:
- virustotal
- abuseipdb
- spamhaus
- googlesafebrowsing
output: reputation_results
step_4_classify_threat:
action: threat_classification
input:
email_analysis: "{{step_1_extract_iocs}}"
domain_analysis: "{{step_2_enrich_domains}}"
reputation: "{{step_3_check_reputation}}"
rules:
- if: "reputation.malicious_count >= 2"
then: "CRITICAL"
- if: "domain_analysis.new_domain and email_analysis.urgency_detected"
then: "HIGH"
- if: "domain_analysis.homoglyph_detected"
then: "HIGH"
- default: "MEDIUM"
output: threat_level
step_5_containment:
action: conditional
condition: "{{step_4_classify_threat.threat_level}} in ['CRITICAL', 'HIGH']"
then:
- action: block_sender_domain
target: email_gateway
- action: quarantine_similar_emails
search: "{{step_1_extract_iocs.template_fingerprint}}"
- action: reset_affected_user_credentials
users: "{{trigger.affected_users}}"
- action: create_incident_ticket
system: servicenow
priority: "{{step_4_classify_threat.threat_level}}"
step_6_notification:
action: notify
channels:
- slack: "#security-incidents"
- email: "soc-team@company.com"
template: se_incident_summarySigma 规则集:社会工程学检测
title: Suspicious Email Forwarding Rule Created
id: b1c2d3e4-5678-90ab-cdef-333333333333
status: experimental
description: 检测邮箱中创建的可疑转发规则,常见于BEC攻击后的持久化
author: SOC Team
date: 2026/07/02
logsource:
product: microsoft365
service: exchange
category: audit
detection:
selection:
Operation: New-InboxRule
Parameters|contains:
- 'ForwardTo'
- 'RedirectTo'
- 'ForwardAsAttachmentTo'
filter_internal:
ForwardTo|endswith: '@company.com'
condition: selection and not filter_internal
level: high
tags:
- attack.collection
- attack.t1114.003
- bec
- social_engineeringtitle: Phishing Kit Infrastructure Detection
id: c2d3e4f5-6789-01ab-cdef-444444444444
status: experimental
description: 通过Web服务器指纹检测已知的钓鱼工具包基础设施
author: SOC Team
date: 2026/07/02
logsource:
category: proxy
product: web_proxy
detection:
selection_url:
cs-uri|contains:
- '/.well-known/captcha/'
- '/gate.php'
- '/login.php?cmd='
- '/process.php?token='
selection_headers:
c-useragent|contains:
- 'Evilginx'
- 'Modlishka'
- 'Muraena'
selection_response:
sc-status: 200
cs-uri|contains|all:
- 'login'
- 'verify'
- 'update'
- 'secure'
condition: selection_url or selection_headers or selection_response
level: high
tags:
- attack.credential_access
- attack.t1566
- phishing
- infrastructure0x0C 公开案例分析
案例一:2023 MGM Resorts 社会工程学攻击
攻击概述:
2023 年 9 月,MGM Resorts International 遭受了一次大规模社会工程学攻击,导致其赌场运营系统瘫痪超过 3 天,造成约 $1 亿美元的损失。攻击者为 Scattered Spider(又名 UNC3944)组织。
攻击链重建:
时间线(UTC):
2023-09-10 14:00 - 攻击者通过LinkedIn收集MGM员工信息
2023-09-10 16:30 - 发现目标IT员工的个人信息
2023-09-10 17:00 - 致电MGM IT服务台,冒充该员工
2023-09-10 17:15 - 成功重置员工MFA/密码(Vishing攻击)
2023-09-10 18:00 - 使用窃取的凭证访问Okta管理控制台
2023-09-10 19:30 - 创建新的管理员账户
2023-09-10 21:00 - 部署LockBit 3.0勒索软件
2023-09-11 02:00 - 酒店系统(房卡/POS/预订)全面瘫痪
2023-09-13 08:00 - MGM宣布系统中断,拒绝支付赎金
2023-09-17 - 系统开始逐步恢复取证发现:
| 取证维度 | 发现 | 证据强度 |
|---|---|---|
| 社工入口 | Vishing攻击IT服务台,利用公开个人信息验证身份 | 🔴 L1-确认恶意 |
| 凭证窃取 | 通过社会工程学重置合法员工凭证 | 🔴 L1-确认恶意 |
| 横向移动 | 通过Okta管理控制台获取广泛系统访问权限 | 🔴 L1-确认恶意 |
| 数据外传 | 通过Mega.nz和Tor网络外传150GB数据 | 🟠 L2-高度可疑 |
| OSINT来源 | 攻击者使用的员工信息来自LinkedIn公开资料 | 🟠 L2-高度可疑 |
IOC 汇总:
domains:
- scattered-spider-c2.xyz
- lockbit-pay-onion.link
ips:
- 185.229.191.xx (C2)
- 45.141.152.xx (exfiltration)
email_addresses:
- fake_it_support@tempmail.org
phone_numbers:
- VoIP numbers used for vishing
tools_used:
- Social engineering toolkit
- LockBit 3.0 ransomware
- Cobalt Strike beacons经验教训:
- IT 服务台的 MFA 重置流程缺乏多因素身份验证 — 仅凭个人信息即可重置
- LinkedIn 等社交平台上的员工信息过于详细,降低了攻击者的侦察成本
- Okta 管理控制台缺乏额外的访问控制层(如硬件安全密钥)
- 缺乏对特权账户的异常行为监控
- 事件响应计划中未充分考虑完全系统瘫痪的场景
案例二:2020 Twitter 比特币欺诈事件
攻击概述:
2020 年 7 月 15 日,多个高知名度 Twitter 账号(包括 Elon Musk、Jeff Bezos、Barack Obama、Kanye West 等)被黑客入侵并发布比特币欺诈推文。攻击者通过社会工程学手段获取了 Twitter 内部工具访问权限。
攻击链重建:
时间线(UTC):
2020-07-14 - 攻击者在多个 Discord/Telegram 频道宣传"Twitter内部工具访问"
2020-07-14 - 一名Twitter员工收到伪装为IT支持的短信
2020-07-14 20:00 - 员工点击钓鱼链接,输入公司VPN凭证
2020-07-14 20:30 - 攻击者使用窃取的VPN凭证访问Twitter内网
2020-07-14 21:00 - 访问Twitter内部支持工具(God Mode / Customer Experience)
2020-07-14 21:30 - 利用内部工具重置目标账号密码
2020-07-15 00:00 - 开始在被盗账号上发布比特币欺诈推文
2020-07-15 01:00 - Twitter检测到异常并禁用相关账号
2020-07-15 02:00 - 开始全面调查取证发现:
| 取证维度 | 发现 | 证据强度 |
|---|---|---|
| 初始入口 | 鱼叉式钓鱼短信(Smishing)针对Twitter员工 | 🔴 L1-确认恶意 |
| 凭证窃取 | 克隆的Twitter VPN登录页面 | 🔴 L1-确认恶意 |
| 内部工具滥用 | 使用合法内部工具执行未授权操作 | 🔴 L1-确认恶意 |
| Discord招募 | 攻击者在Discord上招募内部协助者 | 🟠 L2-高度可疑 |
| 加密货币追踪 | 比特币钱包地址关联到已知诈骗集群 | 🟠 L2-高度可疑 |
IOC 汇总:
domains:
- twitter-verify-support.com (VPN phishing page)
- twitter-login-secure.net (secondary phishing)
ips:
- 104.248.xx.xx (phishing hosting)
bitcoin_addresses:
- bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
- 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
discord_servers:
- Server ID: 7xxxxxxxxx (recruitment server)
telegram_channels:
- @twitter_leaked_access
internal_tools_abused:
- God Mode (account management)
- Customer Experience (account recovery)经验教训:
- 内部工具(God Mode)的访问控制过于宽松,缺乏最小权限原则
- 员工对社会工程学攻击(特别是 Smishing)的安全意识不足
- VPN 登录页面缺乏有效的钓鱼检测机制
- 内部工具的操作审计和异常检测存在延迟
- 高知名度账号缺乏额外的保护层级
- 攻击者利用社交媒体平台本身作为攻击渠道,形成闭环
0x0D 参考资料
Cialdini, R.B. (2009). Influence: Science and Practice. Pearson Education. - 社会工程学六大原则的理论基础,理解攻击者如何利用人类心理弱点。
Hadnagy, C. (2018). Social Engineering: The Science of Human Hacking. Wiley. - 全面的社会工程学攻击方法论和防御策略参考。
Verizon. (2024). 2024 Data Breach Investigations Report (DBIR). https://www.verizon.com/business/resources/reports/dbir/ - 年度数据泄露调查报告,包含社会工程学攻击统计数据。
MITRE. (2024). ATT&CK Enterprise Matrix - Social Engineering Techniques. https://attack.mitre.org/tactics/TA0001/ - MITRE ATT&CK 框架中社会工程学相关技术的官方映射。
FBI IC3. (2024). Business Email Compromise (BEC) - The $29 Billion Scam. https://www.ic3.gov/Media/PDF/2024/2024IC3Report.pdf - FBI 互联网犯罪投诉中心关于 BEC 攻击的年度报告。
Anti-Phishing Working Group (APWG). (2024). Phishing Activity Trends Report. https://apwg.org/trendsreports/ - APWG 季度钓鱼攻击趋势分析报告。
NIST. (2023). SP 800-61r2 Computer Security Incident Handling Guide. https://csrc.nist.gov/publications/detail/sp/800-61/rev-2/final - NIST 事件处理指南,包含社会工程学事件响应流程。
SANS Institute. (2024). SEC504: Hacker Tools, Techniques, and Incident Handling. https://www.sans.org/cyber-security-courses/hacker-tools-incident-handling/ - SANS 安全培训课程中关于社会工程学取证的方法论。
MGM Resorts International. (2023). SEC 8-K Filing - Cybersecurity Incident Disclosure. https://www.sec.gov/ - MGM 关于 2023 年社会工程学攻击事件的官方披露文件。
Twitter. (2020). Update on Recent Account Compromises. https://blog.twitter.com/en_us/topics/company/2020/update-on-recent-account-compromises - Twitter 关于 2020 年大规模账号被盗事件的官方声明。
Europol. (2024). Internet Organised Crime Threat Assessment (IOCTA). https://www.europol.europa.eu/activities-services/main-reports/european-union-internet-organised-crime-threat-assessment-iocta-2024 - 欧盟刑警组织关于互联网有组织犯罪的威胁评估。
CrowdStrike. (2024). Global Threat Report 2024 - Social Engineering as Initial Access Vector. https://www.crowdstrike.com/resources/reports/global-threat-report/ - CrowdStrike 全球威胁报告中关于社会工程学作为初始访问向量的分析。