flawopen.com/Incidents/equifax-struts-ognl-rce-july-2017
●
Equifax · CVE-2017-5638 · July 2017 · CVSS 9.8 · 严重
安全研究 · FlawOpen
Equifax 数据泄露:近 1.5 亿人受影响
CVE-2017-5638 允许攻击者通过 Content-Type 标头在 Equifax 未修补的 Struts 门户注入 OGNL。攻击持续约 76 天,近 1.5 亿人的数据受到影响。[1], [4]
💡 通俗易懂的原理解析 (ELI5)
想象一种邮件系统:它把被拒收的寄件标签放进错误提示。如果提示把标签当作可执行的指令模板,寄件人就能让系统照着指令操作,而不是只报告地址错误。Struts 当时已有修复,但 Equifax 将通知发到了过时的管理员名单,网络扫描也未能发现该门户上的漏洞。[1], [4]
核心概念与专有名词
框架映射
- 本指南所述弱点的 CWE 参考: CWE-20 CWE-917
- CISA KEV — 收录日期 2021-11-03 — CVE-2017-5638
事件演进时间线
2017-03-07
Apache 发布安全公告 S2-045 以及修复版本 Struts 2.3.32 和 2.5.10.1。[1]
2017-05-13
Equifax 后来向 GAO 表示,另一场入侵于 5 月 13 日开始,攻击者从此开始提取个人数据;此前已经发生过一次未经授权的访问,但当时没有窃取数据。[4]
2017-07-29
Equifax 在为一台流量检测设备更新过期证书后,发现了可疑流量。[4]
2017-09-07
Equifax 对外公布数据泄露事件。GAO 后来报告称,至少 1.455 亿美国人的数据和近 100 万美国境外人士的数据受到影响。[4]
攻击执行流程分解
源代码对比:漏洞与安全实现
示意性的 Java 代码,并非 Struts 源代码;Struts 2.3.32 和 2.5.10.1 中的真实修复改变了 multipart 错误消息的构造方式。加固版本使用固定的消息键,只把请求头作为位置参数传入,因此它只会被格式化为文本,绝不会成为表达式的一部分。它还会把任何不像媒体类型的值替换为占位符,这样即使日后修改消息,也无法让请求头重新进入被求值的字符串。[3], [2], [6]
存在漏洞的实现
import com.opensymphony.xwork2.TextProvider;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private final TextProvider text; // resolves messages and evaluates expressions in them
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
// 未处理的请求头会成为待求值消息的一部分。
return text.getText("Invalid content type: " + contentType);
}
}加固后的安全修复
import com.opensymphony.xwork2.TextProvider;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private static final Pattern MEDIA_TYPE = Pattern.compile(
"^[a-z]+/[a-z0-9.+-]+(;\\s*[a-z0-9-]+=[a-z0-9._-]+)*$");
private final TextProvider text;
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
boolean wellFormed = contentType != null && contentType.length() <= 256
&& MEDIA_TYPE.matcher(contentType.toLowerCase(Locale.ROOT)).matches();
// 使用固定的消息键;请求头只能作为位置参数传入,
// 且仅当其格式符合媒体类型时才传入。
return text.getText("upload.invalid.content.type",
new String[] { wellFormed ? contentType : "(malformed)" });
}
}工程与系统安全加固清单
- ✓为每个已部署的应用维护 SBOM,在 S2-045 这类公告发布后几小时内就能回答“我们哪里在运行 Struts 2.3.x?”。[4]
- ✓把漏洞扫描器的目标与资产清单逐一核对;一台从未纳入扫描范围的主机扫描结果干净,并不能证明它已打补丁。[4]
- ✓至少提前 30 天对 TLS 检测和监控设备的证书到期告警;本案中一张过期证书掩盖了数月的数据外泄。[4]
- ✓代码评审时,拒绝任何把拼接后的不可信文本传给消息解析器、模板引擎或表达式求值器的调用;应改为作为参数传入。[6]
- ✓把数据库与面向互联网的应用隔离,不在应用文件中保存凭据,并在应用账号出现异常查询量时告警。[4]
参考来源
- Apache Struts — 安全公告 S2-045(CVE-2017-5638)
- Apache Struts — multipart parser error-message fix (commit 3523064)
- Apache Struts — upstream multipart error-handling fix (commit 6b8272c)
- 美国政府问责局 — 数据保护:Equifax 与联邦机构针对 2017 年泄露事件采取的措施(GAO-18-559)
- CVE-2017-5638 — CVE 记录
- MITRE CWE-917 — 表达式语言注入
- Log4Shell:写一行日志如何变成远程代码执行
- 命令注入 — 完整类别参考