-
-
[原创]《邮件钓鱼攻防工程》Part 3:内容对抗——贝叶斯和NLP
-
发表于: 8小时前 181
-
本文是「邮件钓鱼攻防工程」系列的第三篇。前两篇解决了发信通道和反沙箱。但发信只是一部分,正文被网关过滤,前面的工作全白做。
邮件能不能进网关,内容进网关之后能不能不进垃圾箱,这是两个完全不同的对抗维度。本篇拆邮件安全网关(SEG)最核心的内容检测逻辑:贝叶斯分类器和 NLP 语义引擎,以及工程上的规避思路。
作者: 可惜夜
首发至公众号:Yofune安全研究 欢迎关注。
0x01 对手模型:SEG 内容层的双层检测架构
国内和境外的环境差异直接影响内容对抗的策略选择。境外企业网络的主力 SEG 是 Microsoft Defender for Office 365 和 Proofpoint;国内环境更复杂,大型央企和金融机构普遍部署了国产邮件网关,安全厂商这类产品百花齐放,不一一例举。中小企业大量使用腾讯企业邮、阿里云企业邮,后端反垃圾接的是云端能力。
这些产品的内容检测架构大体一致,底层两层叠加:第一层是贝叶斯概率层,基于词袋模型,SpamAssassin 和 Rspamd 是最典型的开源实现,国产厂商基本在此之上做了中文语料的二次训练;第二层是深度语义层,国内有不少基于 BERT-base 的轻量检测模型,不详细展开,感兴趣的可以搜索关键字。两层检测不是串联阻断,而是评分加权合并。一封邮件贝叶斯层得分很低,但只要深度语义层捕捉到了钓鱼邮件特征,照样被拦。目标是让最终得分低于阈值,需要两层一起对抗。

SpamAssassin 的 Bayes 插件(lib/Mail/SpamAssassin/Plugin/Bayes.pm)
Token 化流程(源码 tokenize 函数位于第 1134-1217 行,_tokenize_line 位于第 1219-1380 行):
原始文本 → 正则提取 → 最少3字符过滤 → SHA1哈希截取40位 → 停用词过滤
Token 提取正则(_tokenize_line 函数内,行 1235-1240):

# 使用正则表达式提取Token:匹配ASCII单词、英文字符,以及多字节的UTF-8编码字符(用于支持中文等非ASCII文本)
s{ ( [A-Za-z0-9,@*!_'"\$. -]+ | # 匹配常规ASCII词元
[\xC0-\xDF][\x80-\xBF] | # 匹配UTF-8双字节字符
[\xE0-\xEF][\x80-\xBF]{2} | # 匹配UTF-8三字节字符(包含常见中文字符)
[\xF0-\xF4][\x80-\xBF]{3} | # 匹配UTF-8四字节字符
[\xA1-\xFF] ) | . } # 匹配ISO-8859等单字符或多字节边缘字符,其余未匹配的单个字符替换为空格
{ defined $1 ? $1 : ' ' }xsge;
SHA1 哈希压缩为 40 位 Key(tokenize 函数内,行 1212):
# 对提取出的Token计算SHA1值,并截取其哈希结果的最后5个字节(40位),作为统计数据库中存储的唯一键值
$tokens{substr(sha1($token), -5)} = $token if $token ne '';
SpamAssassin 实现里有三个硬性设计边界值得利用。
Token 计数上限(行 250):

# 声明在对数似然比排序中参与评分合并计算的最大有效Token数量为150。超过该限制的弱特征Token将被舍弃。
use constant N_SIGNIFICANT_TOKENS => 150;
SpamAssassin 在计算贝叶斯分值时,会把分词拿到的所有 Token 按概率偏差(即该词有多偏向垃圾邮件或正常邮件)进行降序排列,最多只取前 150 个最明显的 Token 参与最终计算,超出 150 个的词直接丢弃。这个 150 的限制是代码里写死的硬性规定。
只要在正文里混入足够多(超过 150 个)的强 Ham 词(即正常业务往来中的高频词),就能把真正敏感的恶意词挤出这 150 个计分窗口,使其不参与最终评分。

Robinson 平滑算法(行 1644-1653,常量在 CombineChi.pm 第 47-50 行定义,FW_S_CONSTANT = 0.030,FW_S_DOT_X = 0.538 * 0.030 = 0.01614):
# 1. 计算原始贝叶斯分类概率
my $prob = ($s * $nn) / ($n * $ns + $s * $nn);
# 2. 如果启用了 Robinson 平滑算法(针对低频出现的 Token)
if (USE_ROBINSON_FX_EQUATION_FOR_LOW_FREQS) {
my $robn = $s + $n; # 该 Token 在所有样本中出现的文档总数
# 使用 Gary Robinson 的 f(w) 公式计算平滑后的概率:
# P_smooth = (s * x + n * P_raw) / (s + n)
$prob = ($Mail::SpamAssassin::Bayes::Combine::FW_S_DOT_X + ($robn * $prob))
/ ($Mail::SpamAssassin::Bayes::Combine::FW_S_CONSTANT + $robn);
}
robn 是词库里记录的该词总频次。塞入拼写奇怪的罕见词或生僻词,平滑公式会直接把它的概率拉回 0.5(代表无特征)。概率为 0.5 的词在排序时垫底,进不了 150 个评分窗口。所以,做正文稀释时,必须使用网关已经见过多次的常规商务词汇,不能胡乱生造冷门词。
得分合并的数学核心,在 CombineChi.pm(行 60-104):

# 1. 累加计算双假设 Fisher 对数概率
foreach my $prob (@$sortedref) {
$S *= 1.0 - $prob; # 假设邮件为 Spam 的概率累乘因子 (1 - P(Spam|Token))
$H *= $prob; # 假设邮件为 Ham 的概率累乘因子 (P(Spam|Token))
# 浮点防下溢处理省略(使用 frexp 分解科学计数法)
}
$S = log($S) + $Sexp * LN2; # 计算假设 Spam 的对数似然
$H = log($H) + $Hexp * LN2; # 计算假设 Ham 的对数似然
# 2. 通过卡方逆累积分布函数 (chi2q) 转换为 p 值评分。
# 传入 $wc (即 $token_count) 代表自由度相关的半值参数,省略了重复的乘除2操作。
$S = 1.0 - chi2q(-2.0 * $S, $wc);
$H = 1.0 - chi2q(-2.0 * $H, $wc);
# 3. 将得分归一化到 [0, 1] 区间,作为最终贝叶斯概率分值
return (($S - $H) + 1.0) / 2.0;
Fisher 算法在数学上默认所有词相互独立。但实际邮件中,词语之间关联性非常强。塞入一整段连贯的商务文本,这些关联词会直接扭曲卡方统计的自由度计算,降低分类器判定的把握,反而容易触发误判放行。
Rspamd 的 Bayes 分类器(src/libstat/classifiers/bayes.c)
和 SpamAssassin 不同,Rspamd 使用的是 OSB(Optimal Score Based)算法。
它的一个核心特点是把单个词(Unigram)的权重设成了 0(行 215)。不管单个词有多敏感,它都不会直接影响最终分值,必须和相邻词语结合组成 Bigram(双词组)或 Trigram(三词组)窗口才会计分。
光堆砌单个单词没用,必须注入连贯的词组或完整的商务短语。在中文场景下,分词器如果把词切得太细,容易破坏 OSB 窗口的统计结构。
Rspamd 里的 Robinson 平滑通过 PROB_COMBINE 宏实现(行 217):
// prob: 原始概率, cnt: Token出现总数, weight: Robinson平滑权重, assumed: 默认假设概率(通常为0.5)
#define PROB_COMBINE(prob, cnt, weight, assumed) (((weight) * (assumed) + (cnt) * (prob)) / ((weight) + (cnt)))
在 bayes_classify_token 函数(行 227-324)中被调用:
// 计算 w
w = (fw * total_count) / (1.0 + fw * total_count);
// 平滑合并概率
bayes_spam_prob = PROB_COMBINE(spam_prob, total_count, w, 0.5);
Rspamd 统计 Token 标志位定义(src/libserver/word.h 与 src/libstat/tokenizers/tokenizers.c)
分词阶段,Rspamd 会在 src/libserver/word.h(行 34-47)中为 Token 标记不同的 Flag,这些属性采用 bitmask 形式定义:
|
标志名称
|
位偏移量
|
掩码实际值
|
安全对抗及分析含义
|
| :-- | :-- | :-- | :-- |
| RSPAMD_STAT_TOKEN_FLAG_TEXT | 1u << 0 | 0x01 |
纯文本内容提取的常规 Token
|
| RSPAMD_STAT_TOKEN_FLAG_META | 1u << 1 | 0x02 |
元数据或指令生成的 Token
|
| RSPAMD_STAT_TOKEN_FLAG_LUA_META | 1u << 2 | 0x04 |
由 Lua 规则接口动态产生的 Token
|
| RSPAMD_STAT_TOKEN_FLAG_EXCEPTION | 1u << 3 | 0x08 |
异常 Token(例如 URL 提取出的特征)
|
| RSPAMD_STAT_TOKEN_FLAG_HEADER | 1u << 4 | 0x10 |
邮件 Header 头部字段提取的 Token
|
| RSPAMD_STAT_TOKEN_FLAG_UNIGRAM | 1u << 5 | 0x20 |
单字/单词一元 Token 标志
|
| RSPAMD_STAT_TOKEN_FLAG_UTF | 1u << 6 | 0x40 |
UTF-8/Unicode 多字节文本 Token
|
| RSPAMD_STAT_TOKEN_FLAG_NORMALISED | 1u << 7 | 0x80 |
ICU 库成功进行规范化转换的 Token
|
| RSPAMD_STAT_TOKEN_FLAG_STEMMED | 1u << 8 | 0x100 |
已成功执行词干提取转换的 Token
|
| RSPAMD_STAT_TOKEN_FLAG_BROKEN_UNICODE | 1u << 9 | 0x200 |
Unicode 序列规范化失败或格式损坏。该标志会被用于将任务全局标志标记为 RSPAMD_TASK_FLAG_BAD_UNICODE,但不会在 Bayes 概率计算中对 Token 权重进行“降权 50%”的操作。
|
| RSPAMD_STAT_TOKEN_FLAG_STOP_WORD | 1u << 10 | 0x400 |
识别为高频中性的停用词
|
| RSPAMD_STAT_TOKEN_FLAG_SKIPPED | 1u << 11 | 0x800 |
属于被衰减或跳过的 Token,不参与评分计算
|
| RSPAMD_STAT_TOKEN_FLAG_INVISIBLE_SPACES | 1u << 12 | 0x1000 |
包含不可见字符的 Token
|
| RSPAMD_STAT_TOKEN_FLAG_EMOJI | 1u << 13 | 0x2000 |
包含 Emoji 表情符号的 Token
|
BROKEN_UNICODE 和 INVISIBLE_SPACES 是判断是否存在异常绕过行为的重要技术线索。若邮件中包含大量非规范 Unicode 或隐藏格式,容易触发网关的异常拦截逻辑。
词干提取与自定义分词(tokenizers.c 约第 600-650 行)
词干提取(Stemming)会消除单词变形的影响,例如 "approved" 与 "approves" 会归并为相同的词干 "approv"。这虽然限制了通过同词变形刷计数的做法,但也意味着如果注入大量 "unanimously",等于在全局给 "unanim" 这一族词干刷了强烈的 Ham 信誉。在中文或日文的实际部署中,如果网关引入了自定义的中文分词器,需要摸清其词典边界,OOV的打散退化往往会减弱稀释效果。
所有 Token 在生成时都会计算 Mum-hash 用于 Redis 端的去重。即使使用等价 Unicode 字符替换,哈希值的改变也会使其被当作新词统计。
自动学习决策(lualib/lua_bayes_learn.lua,行 184、368)
在 can_learn() 逻辑中,Rspamd 设置了自适应防过拟合的保护机制。默认的置信度阈值为 min_prob = 0.95:
-- 判断当前邮件的贝叶斯分类置信度是否已经达到阈值(默认为0.95)
in_class = prob >= (probability_opts.min_prob or probability_opts.spam_min or 0.95)
if in_class then
-- 若系统已经足够确信,则拒绝进一步将其加入训练库中,以防止模型过拟合
return false, reason, ctx.result
end
如果稀释文本成功将评分卡在拦截线以下,且置信度未达到 0.95,则分类器既不会拦截该邮件,也无法从中学习到最新的特征漂移,从而提供了一个稳定的绕过时间窗口。

Rspamd 的神经网络插件(src/plugins/lua/neural.lua)
除了贝叶斯层,Rspamd 默认启用了 ANN(神经网络)分类插件。
其后置过滤器回调 ann_scores_filter(源码约在 121 行)在贝叶斯分类器之后执行。这意味着神经网络看到的特征已经经过了贝叶斯层的预处理。
特征提取依赖以下五个 Provider 接口(源码第 33-37 行):
-- 动态加载这五个特征提供者模块
pcall(require, "plugins/neural/providers/llm") -- 外部LLM嵌入特征
pcall(require, "plugins/neural/providers/symbols") -- 规则得分缓存收集
pcall(require, "plugins/neural/providers/text_hash") -- 局部特征哈希
pcall(require, "plugins/neural/providers/fasttext_embed") -- FastText 词嵌入
pcall(require, "plugins/neural/providers/static_embed") -- 静态词嵌入
在生产环境中,通常仅启用 symbols 和 text_hash。
在 Hybrid特征提取模式下,神经网络把贝叶斯的打分直接当做特征输入的一部分。贝叶斯分数被稀释词干扰降下来后,这个偏低的分数被神经网络直接吃进去。神经网络虽然学到了”低贝叶斯分 = Ham”,但它无法区分这封信是因为干净还是被掺了沙子。
网关串行审计架构在这里存在盲区。
网关开启了 PCA 特征降维时,多封邮件里注入的相似良性文本被当作主成分保留,而原本维度较低的恶意敏感词特征反而容易在降维过程中被过滤掉,导致神经网络失效。
权重继承机制(行 1452)通过 Redis 的 zset 查找 providers_digest 匹配的旧 profile 并复制权重,避免在配置更新时重新训练。
五个 Provider 绝大部分仍然依赖对 Token 流的切分。一旦贝叶斯层削弱了 Token 的区分力,神经网络所接收的输入特征也会随之失效。
Rspamd LLM Provider 的底层细节(llm.lua)
在 LLM Provider 的工程设计中,有两个机制值得关注:
拼装输入文本:collect_async 中,输入文本强制按主题优先(Subject first)原则拼接:
"Subject: " + 邮件主题 + "\n" + 正文内容
确保在嵌入向量的位置编码中,高信息价值的主题行始终被置于最前,避免因长文本截断而丢失关键信息。
路由与缓存:该插件支持根据邮件检测语言路由到不同的第三方模型,且使用了 Redis 进行本地缓存(TTL 默认 24 小时):
Redis 缓存机制引入了新的规避面。已知目标网关开启了该缓存时,可以提前发送一封内容正常的良性邮件(使用特定主题词),让网关缓存下该特征。后续发送带有相同主题词的邮件即可命中该干净缓存,绕过实时审计。
SpamAssassin 的独立神经网络插件(NeuralNetwork.pm)
SpamAssassin 的 NeuralNetwork 插件(v0.11.2)走另一条路:不依赖贝叶斯分,而是维护自己独立的词汇表与特征流水线。
隐藏层结构(源码第 767-770 行、第 1327-1330 行):
# 隐藏层1节点数计算:≈ √输入维度 × 0.25 (向上取整)
my $num_hidden1 = int(sqrt($num_input) * 0.25 + 0.5);
$num_hidden1 = 4 if $num_hidden1 < 4; # 最少4个节点
# 隐藏层2节点数计算:为隐藏层1节点数的一半
my $num_hidden2 = int($num_hidden1 / 2);
$num_hidden2 = 2 if $num_hidden2 < 2; # 最少2个节点
输入层映射至该双隐层结构,并在单输出神经元输出结果。
词表修剪卡方计算(_chi2_score,行 1065):
# 计算特定词项(term)的卡方得分,用于过滤全局区分度弱的特征词
sub _chi2_score {
my ($spam, $ham, $total_spam, $total_ham) = @_;
my $total = $total_spam + $total_ham;
return 0 unless $total > 0;
my $total_spam_noterm = $total_spam - $spam; # 未包含该词的Spam文档数
my $total_ham_noterm = $total_ham - $ham; # 未包含该词的Ham文档数
# 分母为各维频数的乘积
my $denom = ($spam+$ham) * ($total-$spam-$ham) * $total_spam * $total_ham;
return 0 unless $denom > 0;
# 卡方检验矩阵交叉相乘平方差计算,提取鉴别度分值
return ($total * ($spam*$total_ham_noterm - $ham*$total_spam_noterm)**2) / $denom;
}
在词汇表达到 10,000 的上限进行清理时,其卡方算法偏向于删除低频但有高特异性的词,而保留高频的中性词。使用高信誉的常规商务词汇进行稀释,它们不容易在词汇表清理中被剔除。
类别加权训练(行 817-825):
# 根据训练样本中Spam与Ham的文档比例,为少数类乘以一个反比权重,最大加权值为4.0倍
if ($isspam) {
$class_weight = ($spam_docs > 0) ? $ham_docs / $spam_docs : 1.0;
} else {
$class_weight = ($ham_docs > 0) ? $spam_docs / $ham_docs : 1.0;
}
$class_weight = 1.0 if $class_weight < 1.0;
$class_weight = 4.0 if $class_weight > 4.0;
my $weighted_epochs = int($train_epochs * $class_weight) || 1;
大量发送被标记为 Ham 的稀释邮件,会稀释掉 ham_docs/spam_docs 的权重比例,直接把整个模型的权重偏向有利于 Ham 判定的方向。
在重训计算”类原型向量”时,一段周期内持续发送含有良性术语的稀释信,这些词录入词表后直接毒化类原型向量,在重训后整体带偏神经网络的判断基准。
SpamAssassin 神经网络插件有四道硬性检测门槛:邮件长度小于 256 字符,或命中词汇表的有效词少于 10 个时,系统直接跳过神经网络检测,退回到贝叶斯层。
正文中把可见字符控制在 200~250 字符(大约 3、4 句话),然后将大段的稀释语料通过 CSS 隐藏(不可见语料不计入正文字数),神经网络就会因为有效字数不足而直接放弃检测,退回贝叶斯层,此时隐藏稀释词已经绕过了它。
其他方案对比:无原生 ML 的 SMTP 架构
Haraka(Node.js 开发)的内容检测插件不含原生 ML/NLP 组件,只做外部 Rspamd/SpamAssassin 接口转发,或者用 karma 等插件按硬编码规则对信誉、SPF 状态做加减分。自建 Node.js 邮件网关在这块很脆弱——绕过外部检测器之后,本地几乎没有防御。yangdongchao/mailscanner 这类 PyTorch 学术分类器就更不用说了,完全没考虑工程对抗。
0x02 维度一:HTML 多态变异
网关审计 HTML 时,通常会将其解析为 DOM 树并计算指纹(如 Fuzzy Hash)。短时间内发送的多封邮件在 DOM 树特征上相似度过高,就会被判定为已知模板的变种。
对抗方法:生成 HTML 时对每个收件人的邮件单独做变异编译,用多态方式扰乱 DOM 特征。
2.1 结构注入(Structural Injection)
编译引擎会在 DOM 树结构中动态引入以下变异:
• 随机嵌套:在
<body>路径下随机引入 1~3 层无样式的<div>/<span>嵌套,其 class 类名完全动态随机生成。• 空节点引入:随机插入多个
display:none占位符。视觉上不可见,但会影响 DOM 解析树的深度和兄弟节点计数,直接改变特征 Hash 值。• 属性顺序置乱:在生成 HTML 元素时,随机打乱各标签属性的输出顺序(例如将 href, style, class 顺序倒置)。许多 DOM 解析器是顺序敏感的,这会改变网关的 Hash 指纹。
2.2 等效标签置换(Tag Normalization Bypass)
编译引擎结合转换映射,随机置换等效标签:
|
原始标签
|
变异标签
|
NLP 解析差异
|
| :-- | :-- | :-- |
| <b> | <strong> |
语义标签 vs 样式标签
|
| <i> | <em> |
同上
|
| <strike> | <s>
/ <del>
|
新旧语法标准混杂
|
| <center> | <div style="text-align:center"> |
标签 vs CSS 样式
|
| <font> | <span style="..."> |
传统样式 vs CSS 样式
|
每次生成以 50% 的概率随机置换,破坏基于标签分布的特征词频统计。
2.3 随机 CSS 格式化
模板里的类名均采用随机字符代替,同时内联 CSS 样式的属性格式也随机选择:
/* 格式一 — 十六进制 */
color: #ff0000; margin: 10px;
/* 格式二 — rgb函数 */
color: rgb(255, 0, 0); margin: 10px 10px 10px 10px;
/* 格式三 — hsl简写 */
color: hsl(0, 100%, 50%); margin: 10px;
网关提取 CSS 的正则过滤规则因此无法成功匹配。
0x03 维度二:语义稀释——拉低贝叶斯评分
语义稀释的数学原理是通过在正文引入大量的强 Ham 词,来拉低贝叶斯联合概率分值,让原本超标的分数降到网关拦截线以下。但工程落地中需要考虑这些稀释词的注入方式,以对抗网关的可见性检测。
3.1 数学原理
贝叶斯的代数加和公式为:
一封包含”立即登录”、”验证账号”等 10~15 个强敏感词的钓鱼邮件,判定值容易突破 +60(网关拦截线通常为 +40)。
不修改原有核心文案,通过在正文中注入大量强 Ham Token(每个词贡献 -2 至 -5 的得分),将联合分数拉低:
当 Misplaced & 时,系统会自动将该邮件归为 Ham 类别。
根据工程实践,稀释注水词量的注入公式为:
TaiGong 设置了三档注水等级:
|
级别
|
注水比例 (DilutionRatio)
|
适用场景
|
| :-- | --: | :-- |
|
Light
|
2.0
|
对付消费级邮(Gmail / Outlook)
|
|
Standard
|
5.0
|
对付企业 邮服环境
|
|
Aggressive
|
10.0
|
高防目标(金融、央企)
|

3.2 七种文本注入手段
按检测难度从低到高排列:
#1 HTML 注释区
<!-- quarterly budget review meeting scheduled for next Thursday -->
HTML 注释区注入最基础——直接把词写在 <!-- --> 里。现代网关分词时一般会自动过滤注释,只对极少数老旧系统有效。
#2 同色极小字体
<span style="font-size:1px;color:#ffffff">budget allocation approved</span>
用 CSS 把字体设成 1px、颜色设成与背景相同。简单网关上有效,但带可见字符占比审计的高级网关会触发惩罚。
#3 overflow:hidden 与零高度容器
<div style="height:0;overflow:hidden;max-height:0;opacity:0">
team meeting scheduled for Thursday
</div>
overflow:hidden 配合零高度容器(height:0; max-height:0; opacity:0),比单一隐藏样式更稳定,不同环境下表现一致。
#4 ARIA 语义隐藏
<span aria-hidden="true" role="presentation" style="position:absolute;left:-9999px">
project milestone review
</span>
ARIA 无障碍属性也能用来隐藏:aria-hidden="true" role="presentation" 配合 left:-9999px 绝对定位,读屏器会跳过这段内容,但直接提取文本的网关引擎照读不误。
#5 mime multipart/alternative 差异注入复合格式邮件走 multipart/alternative 结构:text/plain 放良性长文本,HTML 放实际内容。网关为了性能往往初筛只扫 plain 部分——第一轮评分就这样拉下来了。
#6 CSS 伪元素 content 注入
<style>
.d1::after { content: "budget allocation quarterly review"; }
.d2::before { content: "team meeting scheduled for Thursday afternoon"; }
</style>
<div class="d1"></div>
<div class="d2"></div>
CSS 伪元素 ::before / ::after 注入则更难检测。稀释内容写入样式表的 content 属性,普通文本提取器只读 DOM textContent,读不到 CSSOM 的计算值。
#7 JavaScript 动态延时渲染
<div id="dilution_container" style="display:none">...</div>
<script>
setTimeout(function() {
document.getElementById('dilution_container').style.display = 'block';
}, 5000);
</script>
JavaScript 延时渲染是检测难度天花板。容器初始状态设为隐藏,JS 延时数秒后再改为显示,网关一般不会等待 JS 执行完毕,扫描时这部分词被忽略,用户端依然可以渲染。
3.3 语料库筛选
语料库分三层:
• Layer 1 — 通用商务(基础 500条):全是项目审批、会议通知等纯商业文案(比如“本季度财务预算已经提交审阅”)。
• Layer 2 — 行业特异(约 200条):根据每个行业所接触的邮件预料不同而塞入不同的词组进行干扰,因为目标行业的网关本就会经常放行这类词,把它们视为极高 Ham 概率特征。
• Layer 3 — 动态生成:调用 LLM 获取当天或本周的商业、时政热点,组装成和发信场景匹配的模板,也许能绕过网关的缓存名单?
Token 筛选准则:SpamAssassin 分值为负、长度超 5 字符、不带任何 URL 链接,三个条件缺一不可。
0x04 未完成的维度三:HTML5 Smuggling 流量层绕过
绕过网关内容过滤后,进一步投送可执行程序(Cobalt Strike 载payload)时,还需要解决网络流量和附件特征的检测问题。
HTML5 Smuggling 提供了流量层免杀的思路:让文件在受害者本地的浏览器内存中生成,而不是直接在网络上传输。
暂时没有找到好的思路实现这一目标,目前来说这只是一个设想,因此也不过多套困了,具体的思路可以搜索相关关键词。
4.1 落地页上的对抗
TaiGong 实战中,目前只是语义稀释配合落地页一起用。
效果不算很好,究极的理想状态就是我说的:受害者点击链接进入落地页后,后台执行 Smuggling ,同时往网关塞入大量良性商务免责声明等 Ham 文本,能够有效阻挡安全策略对 URL 的爬行和标记就如愿了。
0x05 结语
从开源产品来看,策略应该都是大同小异的,就是不清楚闭源设备有LLM的参与之后,进化会到何种地步。
更根本的防线在终端上。
参考资料:
• _Proofpoint: Introduction to HTML Smuggling, _b9fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2H3M7X3!0G2k6Y4m8G2K9h3&6@1i4K6u0W2j5$3!0E0i4K6u0r3N6i4y4Q4x3V1k6T1L8r3!0Y4i4K6u0r3N6r3S2J5k6h3q4@1i4K6u0V1K9h3&6K6K9h3N6Z5N6q4)9J5c8X3W2F1N6s2u0G2k6s2g2U0N6r3W2G2L8W2)9J5k6r3S2@1L8h3I4Q4x3X3c8K6L8i4g2Y4k6$3I4A6L8X3M7`.
• _Proofpoint: HTML Smuggling Evolves - Threat Actor Adoption and Detection, _1b5K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2H3M7X3!0G2k6Y4m8G2K9h3&6@1i4K6u0W2j5$3!0E0i4K6u0r3N6i4y4Q4x3V1k6T1L8r3!0Y4i4K6u0r3N6r3S2J5k6h3q4@1i4K6u0V1K9h3&6K6K9h3N6Z5N6q4)9J5c8X3S2@1L8h3I4Q4x3X3c8K6L8i4g2Y4k6$3I4A6L8X3N6Q4x3X3c8W2N6X3!0D9N6X3g2K6
• _SpamAssassin Bayes Scoring Documentation, _c09K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6$3W2C8K9g2)9J5k6h3q4H3j5h3y4Z5k6g2)9J5k6h3!0J5k6#2)9J5c8X3y4G2L8X3k6D9N6h3g2F1j5$3g2Q4x3V1k6V1K9i4y4H3L8r3q4&6i4K6u0r3f1#2m8m8e0f1q4e0f1@1q4e0f1@1W2z5i4K6u0r3b7X3q4&6k6i4x3`.
• _Rspamd Documentation: Statistics and Bayes, _41aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8$3y4Q4x3V1k6U0L8$3&6X3K9h3N6#2M7X3q4@1K9h3!0F1i4K6u0r3M7%4c8S2N6r3W2K6N6r3W2U0i4K6u0W2K9s2c8E0L8l9`.`.
• _Microsoft 365 Defender Anti-Phishing Policy Reference, _a2cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8X3g2F1i4K6u0V1N6i4y4Q4x3V1k6V1k6h3k6W2L8X3c8W2M7W2)9J5k6r3!0X3k6X3W2U0k6g2)9J5k6o6x3$3y4g2)9J5c8X3q4F1N6r3W2Q4x3X3c8H3K9r3W2K6K9r3W2F1k6#2)9J5k6s2m8G2L8r3W2U0K9h3g2K6i4K6u0V1j5h3u0G2N6i4b7`.
• _IcedID Banking Trojan Leveraging HTML Smuggling (2021), _bf0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2E0j5$3q4X3k6h3g2Q4x3X3g2U0L8$3#2Q4x3V1k6T1L8r3!0Y4M7#2)9J5c8X3!0@1K9r3g2J5i4K6u0V1j5X3I4G2k6%4y4Q4x3V1k6E0j5$3q4X3k6h3g2Q4x3X3c8D9j5h3u0K6i4K6u0r3K9h3y4W2k6r3W2V1i4K6u0V1j5X3q4F1K9$3W2F1k6#2)9J5k6s2c8J5L8$3A6S2L8W2)9J5k6s2g2K6k6i4y4Q4x3X3c8Z5N6r3#2D9i4K6u0V1M7$3#2#2k6$3N6D9K9h3&6Y4i4K6u0V1N6r3g2U0K9r3&6A6M7i4g2W2i4K6u0r3
• _SpamAssassin Bayes Plugin source_aedK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7r3q4U0K9r3g2Q4x3V1k6K6M7r3q4E0j5i4y4K6j5i4y4K6K9h3&6Q4x3V1k6T1L8r3!0T1i4K6u0r3N6s2u0#2L8X3E0Q4x3V1k6D9K9h3u0Q4x3V1k6y4j5h3W2D9i4K6u0r3f1%4m8S2L8f1q4K6M7$3q4K6M7$3W2F1i4K6u0r3f1r3I4#2k6$3W2F1i4K6u0r3b7X3q4&6k6i4y4Q4x3X3g2H3L8b7`.`.
• _SpamAssassin CombineChi source: _eddK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7r3q4U0K9r3g2Q4x3V1k6K6M7r3q4E0j5i4y4K6j5i4y4K6K9h3&6Q4x3V1k6T1L8r3!0T1i4K6u0r3N6s2u0#2L8X3E0Q4x3V1k6D9K9h3u0Q4x3V1k6y4j5h3W2D9i4K6u0r3f1%4m8S2L8f1q4K6M7$3q4K6M7$3W2F1i4K6u0r3b7X3q4&6k6i4y4Q4x3V1k6o6L8$3#2T1K9h3&6W2b7$3S2A6i4K6u0W2M7r3@1`.
• _Rspamd Bayes classifier source: _b14K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3M7%4u0U0i4K6u0r3L8r3W2T1M7%4c8S2N6q4)9J5c8X3y4D9j5i4y4K6K9h3k6A6k6i4u0K6i4K6u0r3j5X3q4&6k6i4y4Q4x3X3g2U0
• _Rspamd Neural Network plugin source: _8a2K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3M7%4u0U0i4K6u0r3M7r3I4#2k6$3W2F1M7#2)9J5c8X3I4#2j5g2)9J5c8X3&6W2N6i4u0S2L8q4)9J5k6h3I4#2j5b7`.`.
• _Rspamd Neural Network providers (LLM): _efeK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3L8s2g2S2L8r3W2T1i4K6u0r3M7r3I4#2k6$3W2F1M7#2)9J5c8X3&6W2N6i4u0S2L8q4)9J5c8Y4m8J5L8%4k6A6k6r3g2J5M7#2)9J5c8X3I4D9L8g2)9J5k6h3I4#2j5b7`.`.
• _Rspamd Tokenizer source: _405K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3M7%4u0U0i4K6u0r3L8r3W2T1M7%4c8S2N6q4)9J5c8Y4c8G2K9$3g2F1K9i4A6W2M7Y4y4Q4x3V1k6@1L8$3E0W2L8X3W2*7k6i4u0K6i4K6u0W2j5H3`.`.
• _Rspamd Bayes autolearn: _178K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6J5M7%4m8S2L8h3c8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3L8s2g2S2L8r3W2T1i4K6u0r3L8s2g2S2i4K6g2X3j5X3q4&6k6i4y4Q4y4h3k6D9k6h3q4J5L8W2)9J5k6h3I4#2j5b7`.`.
• _SpamAssassin NeuralNetwork Plugin source: _15fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7r3q4U0K9r3g2Q4x3V1k6K6M7r3q4E0j5i4y4K6j5i4y4K6K9h3&6Q4x3V1k6T1L8r3!0T1i4K6u0r3N6s2u0#2L8X3E0Q4x3V1k6D9K9h3u0Q4x3V1k6y4j5h3W2D9i4K6u0r3f1%4m8S2L8f1q4K6M7$3q4K6M7$3W2F1i4K6u0r3f1r3I4#2k6$3W2F1i4K6u0r3e0X3g2#2M7X3q4D9e0X3g2@1N6$3!0J5K9#2)9J5k6i4m8E0
• _Haraka Plugin Ecosystem: _e1fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5j5i4u0S2K9$3q4Q4x3V1k6t1j5i4u0S2K9$3p5`.
• _yangdongchao/mailscanner (Python ML): _376K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6&6j5h3&6Y4k6r3!0F1k6$3y4Z5j5h3!0Q4x3V1k6E0j5h3W2D9M7$3y4S2L8X3&6W2M7R3`.`.