第一章:Go上传模块安全漏洞的全景概览
Go语言因其并发模型与静态编译特性被广泛用于构建高吞吐API服务,其中文件上传功能常通过r.ParseMultipartForm()配合r.FormFile()实现。然而,这一看似简洁的流程在生产环境中暴露出多维度安全风险:未经约束的内存分配、路径遍历、恶意文件类型绕过、以及临时文件残留等,均可能被攻击者利用触发RCE、DoS或横向渗透。
常见漏洞类型与成因
- 未限制上传大小导致内存耗尽:
ParseMultipartForm(0)启用无限解析,攻击者发送超大multipart/form-data可迅速耗尽服务器内存; - 文件名未净化引发路径遍历:直接拼接
filepath.Join(uploadDir, filename)且未校验filename(如../../../etc/passwd),造成任意文件写入; - Content-Type绕过执行恶意代码:仅依赖HTTP头
Content-Type判断文件类型,忽略实际文件魔数(magic bytes),导致.jpg.php类双扩展名文件被执行; - 临时文件未清理引发磁盘占满:
FormFile生成的临时文件若未显式os.Remove(),在异常退出或高并发下持续累积。
典型危险代码示例
func uploadHandler(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(0) // ⚠️ 无大小限制,极易OOM
file, _, err := r.FormFile("file")
if err != nil { panic(err) }
defer file.Close()
// ⚠️ 未校验filename,存在路径遍历风险
dst, _ := os.Create("/uploads/" + r.FormValue("filename"))
io.Copy(dst, file) // 直接写入,无类型/内容检查
}
安全加固关键实践
- 设置硬性内存与磁盘配额:
r.ParseMultipartForm(32 << 20)(32MB上限); - 使用
filepath.Base()剥离路径组件,并白名单校验扩展名; - 读取文件前512字节校验魔数(如
net/http.DetectContentType); - 上传后立即重命名文件为UUID+安全扩展名,并设置
0600权限; - 在
defer中确保临时文件清理,或使用io.Pipe流式处理避免落盘。
| 风险点 | 推荐检测方式 | 修复优先级 |
|---|---|---|
| 无大小限制 | 检查ParseMultipartForm参数是否为0 |
高 |
| 路径遍历 | strings.Contains(filename, "..") |
高 |
| 类型伪造 | file.Header.ContentType vs 实际魔数 |
中 |
| 临时文件残留 | lsof -p <PID> \| grep /tmp |
中 |
第二章:Go标准库与第三方库文件上传机制深度剖析
2.1 net/http中multipart/form-data解析流程源码级逆向
net/http 对 multipart/form-data 的解析始于 Request.ParseMultipartForm(),其核心委托给 mime/multipart.Reader。
解析入口与边界提取
func (r *Request) ParseMultipartForm(maxMemory int64) error {
r.multipartForm = nil
r.form = make(url.Values)
r.multiparts = make(map[string][]*multipart.Part)
mr, err := r.MultipartReader() // ← 构建 multipart.Reader,从 Header 中提取 boundary
if err != nil { return err }
// ...
}
MultipartReader() 调用 mime/multipart.NewReader(r.Body, boundary),其中 boundary 来自 Content-Type: multipart/form-data; boundary=----xyz。该边界字符串被严格校验并用于分割 part。
Part 解析状态机
graph TD
A[Read boundary line] --> B{Valid?}
B -->|Yes| C[Parse headers]
B -->|No| D[EOF or error]
C --> E[Stream body until next boundary]
E --> A
关键字段映射表
| Header 字段 | Go 结构体字段 | 用途 |
|---|---|---|
Content-Disposition |
Part.Header |
提取 name, filename |
Content-Type |
Part.Header |
决定 io.Reader 类型 |
Content-Transfer-Encoding |
忽略(HTTP 不使用) | 兼容性保留字段 |
2.2 mime/multipart.Reader边界处理缺陷的实证复现与PoC构造
复现环境准备
- Go 版本:1.21.0(含
mime/multipartv1.21.0) - 关键触发条件:
boundary字符串末尾缺失换行符,且紧邻--终止标记
PoC 构造核心逻辑
// 构造非法 boundary:末尾无 \r\n,且后续紧跟 "--"
body := []byte(
"--boundaryX\r\n" + // 正常分隔头
"Content-Disposition: form-data\r\n\r\n" +
"test\r\n" +
"--boundaryX" + // ❌ 缺少 \r\n,Reader 误判为新 part 起始
"--" + // 实际应为终止标记,但被吞并解析
)
r := multipart.NewReader(bytes.NewReader(body), "boundaryX")
逻辑分析:
multipart.Reader在readLine()中依赖\r\n切分 boundary 行;当--boundaryX--被拆分为--boundaryX(误作 part header)+--(孤立字符串),导致后续NextPart()返回nil, io.ErrUnexpectedEOF,但内部状态已错位,引发内存越界读或 panic。
关键触发路径
| 输入形态 | Reader 行为 | 后果 |
|---|---|---|
--boundary\r\n |
正常识别分隔符 | ✅ 安全 |
--boundary-- |
识别为终止单元 | ✅ 安全 |
--boundary--(无换行) |
将 --boundary-- 拆解为 --boundary + -- |
❌ 状态错乱 |
graph TD
A[readLine] --> B{ends with \\r\\n?}
B -->|No| C[consume until \\n or EOF]
C --> D[误截断 boundary]
D --> E[NextPart 解析失败 + state corruption]
2.3 Content-Disposition字段解析逻辑中的Unicode归一化绕过实践
Unicode归一化与Content-Disposition的冲突点
HTTP响应头中Content-Disposition: attachment; filename="…"若含非ASCII字符,服务端常依赖NFKC归一化校验。但攻击者可混用兼容等价字符(如U+200C零宽非连接符)绕过白名单过滤。
典型绕过Payload构造
# 构造含ZWNJ的filename参数(绕过NFKC归一化检测)
payload = 'file\u200c.txt' # U+200C未被NFKC标准化为普通空格
# 服务端normalize('NFKC', payload) → 'file\u200c.txt'(未变化)
# 但浏览器解析时忽略ZWNJ,实际保存为'file.txt'
该payload利用NFKC不移除控制字符的特性,使服务端校验与客户端渲染产生语义分歧。
绕过路径对比表
| 归一化方式 | 输入 file\u200c.txt |
输出 | 是否触发绕过 |
|---|---|---|---|
| NFKC | file\u200c.txt |
file\u200c.txt |
✅ |
| NFC | file\u200c.txt |
file\u200c.txt |
✅ |
防御建议
- 强制使用
NFKD+ 过滤控制字符(unicodedata.category(c) == 'Cf') - 对
filename*参数优先采用RFC 5987编码而非直接归一化
graph TD
A[原始Content-Disposition] --> B{是否含U+200C/U+200B?}
B -->|是| C[服务端NFKC不变]
B -->|否| D[正常归一化]
C --> E[浏览器忽略ZWNJ→文件名污染]
2.4 Go 1.19+中filepath.Clean路径规范化失效场景验证
Go 1.19 引入了对 filepath.Clean 的优化,但其在跨文件系统挂载点或符号链接深度嵌套时可能返回非最简路径。
失效典型场景
- 挂载点内含
..与 symlink 交错(如/mnt/app/../real/bin→/mnt/real/bin,但实际应归一为/opt/bin) - Windows 驱动器前缀混用(
C:\foo\..\bar与\\?\C:\foo\..\bar行为不一致)
复现代码示例
package main
import (
"fmt"
"path/filepath"
)
func main() {
p := "/a/b/c/../../d/e/./f"
fmt.Println(filepath.Clean(p)) // 输出: /a/d/e/f —— 正确
p2 := "/mnt/link/../target/file" // link → /opt/base, target → /opt/base/target
fmt.Println(filepath.Clean(p2)) // 输出: /mnt/target/file —— ❌ 未解析 symlink
}
filepath.Clean 仅做字符串规约,不调用 os.Stat 或 filepath.EvalSymlinks,因此无法感知真实文件系统拓扑。参数 p 为纯字符串,不携带上下文元数据。
对比行为差异(Go 1.18 vs 1.22)
| 输入路径 | Go 1.18 输出 | Go 1.22 输出 | 是否解析 symlink |
|---|---|---|---|
/var/log/../www/index.html |
/var/www/index.html |
/var/www/index.html |
否 |
/mnt/symlink/../real/file |
/mnt/real/file |
/mnt/real/file |
否 |
graph TD
A[输入路径] --> B{含 symlink?}
B -->|否| C[纯字符串规约]
B -->|是| D[返回未解析路径]
C --> E[符合 POSIX 纯文本规则]
D --> F[需显式调用 EvalSymlinks]
2.5 基于AST静态分析识别高危UploadHandler代码模式
核心风险模式识别原理
AST(Abstract Syntax Tree)可精准捕获 UploadHandler 类中未经校验的文件写入行为,绕过运行时沙箱限制。
典型危险代码片段
public void handleUpload(HttpServletRequest req) {
String fileName = req.getParameter("filename"); // ❌ 未过滤路径遍历字符
String contentType = req.getContentType(); // ❌ 未校验MIME类型
File target = new File("/uploads/" + fileName); // ⚠️ 直接拼接构造路径
writeToFile(req.getInputStream(), target); // ❌ 无扩展名白名单校验
}
逻辑分析:该方法在AST中表现为 BinaryExpression(+ 拼接)→ MemberExpression(req.getParameter)→ CallExpression(writeToFile),三者形成「输入→拼接→落地」高危链;fileName 参数未经过 Path.of().normalize() 或正则校验,易触发 ../webshell.jsp 路径穿越。
静态检测规则矩阵
| AST节点类型 | 危险特征 | 检出置信度 |
|---|---|---|
MemberExpression |
.getParameter("filename") |
高 |
BinaryExpression |
+ 连接用户输入与固定路径 |
高 |
CallExpression |
new File(...) 或 FileWriter |
中高 |
检测流程图
graph TD
A[解析Java源码为AST] --> B{是否存在getParameter调用?}
B -->|是| C[提取参数名并标记为污点源]
C --> D[追踪污点是否流入File构造/IO写入]
D -->|是| E[触发高危UploadHandler告警]
第三章:从解析缺陷到RCE的链式利用路径建模
3.1 构造恶意filename参数触发目录遍历与任意文件写入
漏洞成因分析
当服务端未校验 filename 参数,直接拼接路径并调用 fs.writeFile() 时,攻击者可利用 ../ 绕过限制。
典型恶意载荷示例
// 攻击请求中 filename 参数值
"../../../../etc/passwd"
该路径经拼接后变为 /var/www/uploads/../../../../etc/passwd,若未规范化即执行写入,将覆盖系统关键文件。
关键防御缺失点
- 未调用
path.normalize()或path.resolve()校验路径合法性 - 未白名单限制文件扩展名(如仅允许
.jpg,.png) - 未验证最终绝对路径是否位于预期根目录内
安全路径校验逻辑
const safePath = path.join(uploadDir, filename);
if (!safePath.startsWith(uploadDir)) {
throw new Error('Directory traversal attempt detected');
}
uploadDir 是预设的安全根目录(如 /tmp/uploads),startsWith 确保结果路径不逃逸。
常见危险组合(payload vs 后果)
| filename 参数 | 实际写入路径 | 风险等级 |
|---|---|---|
../../config.json |
/app/config.json |
⚠️ 高 |
shell.php |
/var/www/shell.php |
⚠️ 中 |
/.ssh/authorized_keys |
/root/.ssh/authorized_keys |
🔥 严重 |
graph TD
A[客户端传入filename] --> B{服务端解析路径}
B --> C[未规范化直接拼接]
C --> D[写入任意位置]
B --> E[调用path.resolve校验]
E --> F[拒绝越界路径]
F --> G[安全写入]
3.2 利用Go模板引擎动态编译特性实现Go代码注入
Go 的 text/template 与 html/template 本身不执行 Go 代码,但结合 go/parser + go/types + go/ast 可在运行时解析、修改并编译模板中嵌入的合法 Go 表达式片段。
模板中嵌入可编译表达式
const tmplStr = `package main
import "fmt"
func Generate() string {
return fmt.Sprintf("Hello, {{.Name}}!") // 注意:此处 .Name 是模板变量,非注入点
}`
该模板本身安全;注入需在 AST 层面将 {{.Name}} 替换为经校验的 strconv.Itoa(42) 等表达式节点,再通过 golang.org/x/tools/go/packages 动态编译。
安全边界控制策略
- ✅ 允许:字面量、标准库函数调用(
time.Now())、简单结构体字段访问 - ❌ 禁止:
os/exec,unsafe, 方法调用链超过2层,未白名单导入
| 风险类型 | 检测手段 | 响应动作 |
|---|---|---|
| 外部包引用 | AST ImportSpec 扫描 | 拒绝编译 |
| 反射或系统调用 | CallExpr 函数名匹配 | 替换为 panic stub |
编译流程示意
graph TD
A[模板字符串] --> B[Parse AST]
B --> C[AST 节点校验与重写]
C --> D[生成临时 .go 文件]
D --> E[调用 go build -o /tmp/f.so -buildmode=plugin]
E --> F[Load plugin 并调用 Symbol]
3.3 shellcode嵌入Go二进制文件头并劫持runtime.init执行流
Go程序启动时,runtime.init函数链在main.main前被调用,其地址由.init_array节或ELF重定位项控制。攻击者可利用此机制,在二进制头部预留空间注入shellcode,并篡改_rt0_amd64_linux入口跳转逻辑。
嵌入位置选择
- ELF头部后、
.text节前的padding区域(需确保p_vaddr == p_paddr且页对齐) - 利用
go:linkname导出runtime·addmoduledata等符号,绕过符号隐藏限制
修改init执行流的关键步骤
- 定位
.init_array中首个函数指针(通常为runtime.main注册点) - 将其覆写为指向嵌入shellcode的VA(需计算ASLR偏移)
- shellcode末尾需调用原
init函数以维持程序逻辑
// shellcode示例(x86_64,位置无关)
mov rax, 0x15 // sys_mprotect
mov rdi, 0x400000 // target page (RIP-relative calc needed)
mov rsi, 0x1000
mov rdx, 7 // PROT_READ|PROT_WRITE|PROT_EXEC
syscall
jmp qword ptr [rip + 0x10] // 跳转至原init函数
此汇编片段首先解除目标页内存保护,再跳转至原始
init入口。rip + 0x10需在构建时动态填充真实地址,依赖objdump -d解析重定位表。
| 技术环节 | 关键约束 | 触发时机 |
|---|---|---|
| shellcode注入 | 必须位于可执行段且页对齐 | go build -ldflags="-s -w"后手动patch |
| init劫持点 | .init_array或__libc_start_main参数 |
_rt0_amd64_linux返回前 |
| 地址计算 | 需结合readelf -l获取基址偏移 |
运行时通过/proc/self/maps校准 |
graph TD A[ELF加载] –> B[解析.init_array] B –> C[覆盖首项为shellcode地址] C –> D[shellcode执行:mprotect+跳转] D –> E[调用原init函数] E –> F[继续Go runtime初始化]
第四章:金融级上传模块加固方案与工程落地
4.1 基于WebAssembly沙箱的上传文件内容动态行为检测
传统服务端静态扫描难以捕获混淆脚本、延迟执行或环境感知型恶意载荷。WebAssembly(Wasm)沙箱提供确定性、隔离性与近原生性能,成为动态行为检测的理想执行环境。
核心检测流程
(module
(func $analyze_entry (param $buf i32) (result i32)
local.get $buf
i32.load ;; 加载文件首块(4字节)
i32.const 0x4D5A ;; PE签名 "MZ"
i32.eq
if (result i32) i32.const 1 else i32.const 0 end)
(export "analyze" (func $analyze_entry)))
该Wasm函数接收内存地址参数 $buf,读取首4字节并比对PE文件魔数 0x4D5A;返回 1 表示疑似可执行文件。沙箱内无系统调用能力,杜绝侧信道泄露。
检测能力对比
| 能力维度 | 静态扫描 | Wasm沙箱动态检测 |
|---|---|---|
| 反混淆支持 | ❌ | ✅(可执行解密逻辑) |
| 环境感知行为识别 | ❌ | ✅(模拟JS宿主API) |
graph TD
A[用户上传文件] --> B[提取二进制片段]
B --> C[Wasm沙箱加载分析模块]
C --> D[执行受限指令序列]
D --> E[捕获内存/控制流异常]
E --> F[生成行为指纹]
4.2 使用go-secure-headers与自定义MIME类型白名单双重校验
Web服务需同时防御HTTP头注入与MIME混淆攻击,单一防护存在盲区。
安全头强化:go-secure-headers配置
import "github.com/utrack/go-secure-headers"
h := secure.New(secure.Options{
ContentTypeNosniff: true,
XFrameOptions: "DENY",
ReferrerPolicy: "strict-origin-when-cross-origin",
})
该配置强制浏览器执行内容类型嗅探禁用、帧隔离及严格来源策略,防止XSS与点击劫持。ContentTypeNosniff是MIME校验前置条件——确保浏览器不绕过响应头自行推断类型。
MIME白名单动态校验
var allowedMIMETypes = map[string]bool{
"application/json": true,
"text/html": true,
"image/png": true,
}
func validateMIME(h http.Header) error {
ct := h.Get("Content-Type")
if ct == "" { return errors.New("missing Content-Type") }
mime, _, _ := mime.ParseMediaType(ct)
if !allowedMIMETypes[mime] {
return fmt.Errorf("disallowed MIME type: %s", mime)
}
return nil
}
解析Content-Type主类型(忽略参数),严格匹配预设白名单,阻断text/html; charset=utf-8等变体绕过。
防御协同机制
| 层级 | 作用 | 失效场景 |
|---|---|---|
| go-secure-headers | 强制客户端遵守安全策略 | 旧版浏览器忽略头 |
| MIME白名单 | 服务端主动拒绝非法类型响应 | 攻击者伪造Content-Type |
graph TD
A[HTTP响应生成] --> B[go-secure-headers注入安全头]
B --> C[Content-Type字段校验]
C --> D{是否在白名单?}
D -->|是| E[返回响应]
D -->|否| F[返回406 Not Acceptable]
4.3 静态文件服务与业务逻辑分离的零信任架构改造
传统单体架构中,静态资源(如 JS/CSS/图片)与 API 逻辑共存于同一进程,违背零信任“默认拒绝、最小权限”原则。改造核心是将静态文件服务剥离为独立可信边界网关。
静态资源路由隔离策略
Nginx 作为边缘零信任代理,仅允许 /static/** 路径直通 CDN,其余路径强制转发至业务网关:
# nginx.conf 片段
location /static/ {
alias /var/www/static/;
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "default-src 'self'";
# 禁止执行、禁止上传、强制只读
}
该配置实现三层防护:路径白名单、MIME 类型锁定、CSP 限制脚本注入;alias 替代 root 防止路径遍历,/static/ 末尾斜杠确保精确匹配。
权限模型对比表
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 认证粒度 | 全局 Session | 每个静态资源 URL 签名验证 |
| 网络拓扑 | 同一 Pod 内互通 | VPC 内 Service Mesh 隔离 |
| 审计日志 | 无资源级访问记录 | S3/CDN 层细粒度审计日志 |
数据同步机制
静态资源经 CI/CD 流水线构建后,通过带签名的 PUT 请求推送到对象存储,业务网关不再持有任何静态文件副本:
graph TD
A[CI Pipeline] -->|Signed Upload| B[S3 Bucket]
B --> C[CDN Edge Nodes]
C --> D[Browser]
D -->|Origin Request| E[Zero-Trust Gateway]
E -->|Validate Token| B
此架构使静态服务具备独立身份、可审计生命周期与网络微隔离能力,业务逻辑层彻底解耦文件 I/O 责任。
4.4 基于eBPF的syscall级上传行为实时审计与阻断
核心原理
通过 tracepoint/syscalls/sys_enter_write 和 kprobe/sys_sendfile 钩子捕获文件写入与网络发送路径,结合 bpf_get_current_comm() 与 bpf_get_current_pid_tgid() 关联进程上下文,实现 syscall 级细粒度观测。
关键过滤逻辑
- 检测
write()目标 fd 是否为 socket(bpf_probe_read_kernel(&f_type, sizeof(f_type), &file->f_mode)) - 提取
sendfile()的源文件路径(需bpf_d_path()辅助,受限于 eBPF 安全边界)
示例 eBPF 过滤代码
// 拦截 write() 调用并判断是否向 socket 写入敏感数据
SEC("tracepoint/syscalls/sys_enter_write")
int trace_write(struct trace_event_raw_sys_enter *ctx) {
pid_t pid = bpf_get_current_pid_tgid() >> 32;
int fd = (int)ctx->args[0];
size_t count = (size_t)ctx->args[2];
// 忽略小数据包(<1KB)降低开销
if (count < 1024) return 0;
// TODO: 实际需查 fd 对应 file 结构体类型(此处简化)
bpf_printk("PID %d write to fd %d (%zu bytes)", pid, fd, count);
return 0;
}
该程序在
sys_enter_write时触发,仅记录参数不阻断;真实阻断需配合tc或xdp+bpf_override_return()(5.15+ kernel),且需启用CAP_SYS_ADMIN。bpf_printk()用于调试,生产环境应替换为ringbuf输出。
审计策略对比
| 维度 | 用户态代理审计 | eBPF syscall 级审计 |
|---|---|---|
| 延迟 | ≥100μs | |
| 绕过风险 | 高(LD_PRELOAD 可绕过) | 极低(内核态钩子) |
| 阻断能力 | 仅日志/告警 | 可 bpf_override_return(ctx, -EPERM) |
graph TD
A[syscall enter] --> B{fd 是 socket?}
B -->|Yes| C[提取进程名/路径]
B -->|No| D[忽略]
C --> E{匹配敏感关键词/路径?}
E -->|Yes| F[ringbuf 日志 + 返回 -EPERM]
E -->|No| G[放行]
第五章:事件溯源与行业防御启示
攻击链还原:从钓鱼邮件到域控沦陷
2023年某金融客户遭遇APT组织攻击,安全团队通过EDR日志、DNS流量镜像和Windows事件ID 4688(进程创建)交叉比对,完整复现攻击路径:恶意宏文档触发PowerShell无文件执行 → 下载Cobalt Strike Beacon → 利用Mimikatz抓取LSASS内存凭证 → 横向移动至域控制器。关键证据链中,DNS请求update-secure[.]top与C2服务器IP 192.168.127.12在防火墙全量日志中存在毫秒级时间关联,成为溯源定案依据。
日志留存策略的实战缺陷
某省级政务云平台因日志轮转策略设置为“仅保留7天”,导致勒索软件攻击发生后无法回溯初始入侵时间点。经审计发现,Syslog服务器未启用TLS加密传输,且日志源设备(如交换机、WAF)时间未统一NTP校准,造成同一攻击行为在不同系统日志中时间戳偏差达18分钟。以下为优化后的日志留存矩阵:
| 设备类型 | 日志类型 | 保留周期 | 存储方式 | 加密要求 |
|---|---|---|---|---|
| 域控制器 | 安全事件日志 | 180天 | 分布式ELK集群 | TLS 1.3+ |
| 网络设备 | NetFlow v9 | 90天 | ClickHouse | AES-256 |
| Web应用防火墙 | HTTP事务日志 | 30天 | 对象存储OSS | KMS托管 |
威胁情报驱动的防御闭环
某能源集团将MISP平台接入SOAR系统,当TI源推送新IOC hash:8a7f9b2e3d1c4a5f6b7e8c9d0a1b2c3d时,SOAR自动执行三阶段响应:
- 在EDR终端批量扫描该哈希值进程;
- 调用防火墙API封禁关联IP
172.16.31.10/32; - 向SIEM注入规则:
event.code:"4688" and process.hash:"8a7f9b2e3d1c4a5f6b7e8c9d0a1b2c3d"。
实测平均响应时间从人工处置的47分钟压缩至93秒。
攻击者TTPs映射实践
根据MITRE ATT&CK框架v13.1,对2024年Q1捕获的127起真实攻击样本进行TTPs标注,发现TOP3技术组合具有强行业特征:
flowchart LR
A[TA0001 - 初始访问] --> B[Phishing- T1566.002]
B --> C[TA0002 - 执行]
C --> D[PowerShell- T1059.001]
D --> E[TA0003 - 持久化]
E --> F[Registry Run Keys- T1547.001]
金融行业样本中T1566.002出现频次达89%,而制造业则以T1190(漏洞利用)为主导(占比73%)。该差异直接指导了客户EDR规则权重调优:金融客户将宏文档检测规则优先级提升至P0,制造业则强化SMB协议异常连接监控。
红蓝对抗验证机制
某运营商在核心网管系统部署蜜罐节点后,发现攻击者在渗透测试中连续3次尝试利用CVE-2021-26855漏洞,但均被WAF规则SecRule REQUEST_HEADERS:User-Agent \"Exchange\" \"id:1001,deny,status:403\"拦截。进一步分析攻击载荷发现,其PowerShell混淆脚本存在固定特征-enc [A-Za-z0-9+/]{100,},据此编写YARA规则并下发至全网终端,两周内捕获23个变种样本。
