CVE learn
directory
CVE is my dream.
早在我上小学时,就觉得 CVE 很帅。步入初中时,在初三那年我拿到了广东电信 IPTV 的通用漏洞。步入社会时,我拿到了 Github 的 Bug bounty。但我始终没有去拿一个 CVE 的编号。因为水是可以水的,但我已经水了 CNVD 和 Hackerone,我希望我的第一个 CVE 编号可以是不那么水的,有一点含金量的。
虽然我并不是 Security 行业或领域的从业人员,只是一个爱好者。但我一直觉得 Security 很帅。同样的,虽然我不是 Design 行业的从业人员,但我觉得可以让自己做出来的 project 会好看,有个性。
我并不是一个专精的人。很多时候我每项感兴趣的领域单拎出来都会被降维打击。虽然我单项领域无法媲美专业的从业者,但我感兴趣的领域都有点建树和作品。将 Security、Design、Animation、Computer 合在一起,与之能媲美的人可能会少那么一点。
而现在,我只有 Design 的代表作,而 Security、Animation、Computer 还没有一个能拿得出手的作品。因此我希望会在未来三年,尽可能的补全这些我感兴趣领域的代表作。可能因为 Work 比较闲的缘故,我的时间总会多那么一点来放到我感兴趣的领域上。即使达不到也没有关系,至少也是努力过了,无论如何三年后的自己总会比现在的自己稍微厉害那么一点点。
因此这个页面会记录一些我感兴趣的 CVE 和 CWE 的编号来让我学习,可能会获取到一些新的思路和对某个方面的理解。来帮助我实现这个目标,顺便看看冷门的 CWE 方向,毕竟 OWASP TOP 10 个人没有太大的兴趣。
CVE-2022-36114
不受控制的资源消耗
利用 Cargo 的 build script 在构建阶段编译第三方的非 Rust code,同时结合 procedural macros,在执行 cargo run
的过程中触发了一个 Zip Bomb 行为。
对于这个问题的修复,rust-lang 已经给出了一个 补丁修复 Zip_bomb 的问题:
// 在解压 .crate 文件(tar.gz)时,限制最多只能解压出 512MB 的内容。
+const MAX_UNPACK_SIZE: u64 = 512 * 1024 * 1024;
同时引入了 LimitErrorReader
: 超过就抛出错误,避免资源被耗尽(比如磁盘写爆)。除此之外并没有对 macros、build 加以限制,只是建议尽可能的用 crates 上提交的 crate。
CVE-2023-40030
{OWASP TOP TEN}: 不恰当的网页生成输入中和 XSS
看到这篇通告的时候,我就回想起为什么我看 cargo build --timings
的时候肿么没想到会有 CWE-79。--timings 是一个生成 build 时间的 html 报告。
可以通过在 Cargo.toml
中加入 features = ["<img src='' onerror=alert(0)"]
来触发一个 CWE-79, 因此对这个问题的修复就是,将之前的 Warning
, 换成了 bail!
机制。
bail!
宏来自 anyhow 用于方便地提前返回错误。
可以看一下 fix pr,非常有意思,比如 validate_feature_name
函数。
原来:遇到非法 feature name,只输出 warning
,流程继续。
if let Some(ch) = chars.next() {Add commentMore actions
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
config.shell().warn(&format!(
"invalid character `{}` in feature `{}` in package {}, \
the first character must be a Unicode XID start character or digit \
(most letters or `_` or `0` to `9`)\n\
{}",
ch, name, pkg_id, FUTURE
))?;
}
}
我本来想看看 2025-6-27 的 src/cargo/core/summary.rs 这个 fix pr 的区别。但我发现了一个流量密码,那就是 Rc → Arc, 问就是 Thread Safety!
现在:遇到非法 feature name,直接 bail!
,构建中止,用户必须修正。
if let Some(ch) = chars.next() {Add commentMore actions
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
bail!(
"invalid character `{}` in feature `{}` in package {}, \
the first character must be a Unicode XID start character or digit \
(most letters or `_` or `0` to `9`)",
ch,
name,
pkg_id
);
}
}
在单元测试中,来验证 feature name 是否合法,例如只有 is_ok()
是合法的,而 is_err()
是不合法的:
#[test]Add commentMore actions
fn valid_feature_names() {
let loc = CRATES_IO_INDEX.into_url().unwrap();
let source_id = SourceId::for_registry(&loc).unwrap();
let pkg_id = PackageId::new("foo", "1.0.0", source_id).unwrap();
assert!(validate_feature_name(pkg_id, "c++17").is_ok());
assert!(validate_feature_name(pkg_id, "128bit").is_ok());
assert!(validate_feature_name(pkg_id, "_foo").is_ok());
assert!(validate_feature_name(pkg_id, "feat-name").is_ok());
assert!(validate_feature_name(pkg_id, "feat_name").is_ok());
assert!(validate_feature_name(pkg_id, "foo.bar").is_ok());
assert!(validate_feature_name(pkg_id, "+foo").is_err());
assert!(validate_feature_name(pkg_id, "-foo").is_err());
assert!(validate_feature_name(pkg_id, ".foo").is_err());
assert!(validate_feature_name(pkg_id, "foo:bar").is_err());
assert!(validate_feature_name(pkg_id, "foo?").is_err());
assert!(validate_feature_name(pkg_id, "?foo").is_err());
assert!(validate_feature_name(pkg_id, "ⒶⒷⒸ").is_err());
assert!(validate_feature_name(pkg_id, "a¼").is_err());
}
CVE-2023-51051
越界访问(out-of-bounds): 访问了本不属于该内存区域的数据。
vm-memory 的 VolatileMemory
实现未对边界进行严格校验,导致有可能读取到非法的内存区域:
VolatileMemory::get_slice
: 返回一段“易失性切片”(VolatileSlice),用于后续的底层操作VolatileMemory::read/write
: 直接从这块内存读/写数据,保证每次都实际发生内存访问VolatileMemory trait 就是定义了一套访问“虚拟机物理内存”或“特殊硬件内存区域”的低层接口,保证每次读写都真实发生,且不被编译器优化。它让不同的底层实现都能用统一的 trait 操作。
但问题来了,这个通告并不涉及 read/write
,更多的是 get_slice
方法,例如:
get_atomic_ref
aligned_as_ref
aligned_as_mut
get_ref
get_array_ref
上述方法有个共同点,都是利用 ref
类方法引起的。它们都尝试返回指向底层内存的 Rust 类型引用(如 &T、&[T]
等),而不是直接读写原始字节数据。它们的实现通常会调用 get_slice,假设返回的内存区域长度足够大。
如果自定义实现的 get_slice 返回的切片长度不足,就会导致这些 ref 方法产生越界引用,造成内存安全风险。例如:
仔细欣赏 fix pr 你会发现:
这些方法内部,都会调用 get_slice(offset, count)
拿到一块内存切片,然后把它 reinterpret_cast 成你要的类型引用。因此给 get_slice
加上 assert_eq!
就意味着:
“如果你要 8 字节引用,必须实际拿到 8 字节的内存切片,否则直接 panic,不做 unsafe 操作!”
CWE-278 还是较为冷门的,毕竟只有一个参考,既 CVE-2005-1724。CWE-278 这个类的漏洞常见表现之一就是 权限限制”。总的来说 umask 是 限制新建文件或目录默认权限的一个机制。,可以看看 1,我清晰记得有时候需要执行什么 bash
脚本的时候,会使用 chmod 777
, 要不然运行不了。
CVE-2005-1724 是一个典型的“配置安全限制无效”型漏洞,使得 NFS 导出的目录被所有人都能访问,造成权限绕过和潜在的数据泄露/篡改风险。其归类于 CWE-278 属于权限/访问控制失效问题。
而 CWE-732 又是 CWE-278 的子类,因此会有两个 CWE 的分配。
比如 CVE-2005-1724 在正常情况下,在导出(export)NFS 文件系统时,通常会用 -network
和 -mask
:
/some/dir -network 192.168.1.0 -mask 255.255.255.0
这样本应只有 192.168.1.0/24 网络内的主机可以访问该目录。但在 Mac OS X 10.4.x 的 NFS 服务实现没有正确处理/生效这些参数。因此构成了 CWE-278
要想理解本次的 CVE,我们首先需要理解标题 Cargo 在解压 crate 包时未按照 umask 限制文件权限。而 umask
是默认权限,翻译成人话就是:
Cargo 在解压 crate 包时,没有按照 umask 分个默认的权限。攻击者可以利用这一点也就是 777
权限构建攻击脚本。
↑ 可能不是很严谨
过去 Cargo 解压 .crate 包时,直接采用了压缩包内的文件权限(比如 777/666),没有结合操作系统的 umask,导致解压后的文件权限过宽,其他本地用户可能能写入、篡改这些源码文件。
在 src/cargo/util/mod.rs
中新增了 get_umask()
函数。以在解压文件时,强制用 umask
限制新文件的权限。:
#[cfg(unix)]
pub fn get_umask() -> u32 {
use std::sync::OnceLock;
static UMASK: OnceLock<libc::mode_t> = OnceLock::new();
// SAFETY: Syscalls are unsafe. Calling `umask` twice is even unsafer for
// multithreading program, since it doesn't provide a way to retrive the
// value without modifications. We use a static `OnceLock` here to ensure
// it only gets call once during the entire program lifetime.
*UMASK.get_or_init(|| unsafe {
let umask = libc::umask(0o022);
libc::umask(umask);
umask
}) as u32 // it is u16 on macos
}
以及很有艺术性的一行 code:
fs::set_permissions(path, Permissions::from_mode(final_mode)).unwrap();
解压 crate 时,读取每个文件的原始权限(如 0o777),实际写入磁盘时,会用 原始权限 & !umask
得到最终权限,再 chmod 到文件上:
assert_eq!(metadata.mode() & 0o777, 0o644 & !umask);