从攻击者角度来看Go1.24的路径遍历攻击防御
目录
一、具体攻击示例
程序
攻击步骤:
二、为什么攻击者能成功?
分析
类比理解
总结
三、TOCTOU 竞态条件漏洞
1、背景:符号链接遍历攻击
2. TOCTOU 竞态条件漏洞
3. 另一种变体:目录移动攻击
4. 问题的核心
四、防御方法
在golang 1.24中,提供了Traversal-resistant file API os.Root
。它通过限制对根目录的访问,提供了一种简单而强大的防御机制。Golang官方称,使用os.RootAPI
可以有效地防止未授权的路径遍历攻击。那Go为什么要绞尽脑汁去堵住这一漏洞,它又是如何起作用的?我们避开一堆晦涩的概念,试着从攻击者角度,就很容易理解了。
一、具体攻击示例
程序
假设你的程序逻辑如下:
// 1. 检查阶段:解析符号链接,确保路径是合法的
cleaned, err := filepath.EvalSymlinks("/tmp/user_upload/a/b/file.txt")
if err != nil {return err
}
if !filepath.IsLocal(cleaned) {return errors.New("路径不安全")
}// 2. 使用阶段:打开文件
f, err := os.Open(cleaned) // 攻击者在这里动手脚!
攻击步骤:
-
初始状态:
-
路径
/tmp/user_upload/a/b/file.txt
是一个普通文件(无符号链接)。 -
你的程序检查时确认它是合法的。
-
-
攻击者发动攻击:
-
在你的程序刚完成检查但还未执行
os.Open
的瞬间,攻击者快速执行以下命令-
# 删除原文件(或目录),替换成符号链接 rm -rf /tmp/user_upload/a/b/file.txt ln -s /etc/passwd /tmp/user_upload/a/b/file.txt
(或者直接移动目录:
mv /tmp/user_upload/a/b /tmp/user_upload/a/b_backup
)
-
-
- 程序实际打开文件时:
-
由于路径已被篡改,
os.Open(cleaned)
会跟随符号链接,意外打开/etc/passwd
。
-
二、为什么攻击者能成功?
分析
-
文件系统操作不是原子的:
-
检查和打开是两个独立的系统调用,操作系统会在这之间调度其他进程(包括攻击者的恶意操作)。
-
-
攻击者可以不断尝试:
-
攻击者可以用脚本反复运行符号链接替换操作,只要有一次成功抢占时间窗口,就能达成攻击。
-
-
多核CPU加剧问题:
-
现代多核系统并行执行任务,进一步扩大了竞态条件的可能性。
-
类比理解
想象以下场景:
-
你是一名保安,检查访客的身份证(检查阶段),确认无误后允许进入大楼。
-
但在你低头登记时(检查和放行之间的时间差),攻击者快速调包了身份证。
-
你根据登记表放行时,实际进入的是冒名顶替者(恶意符号链接)。
总结
现在我们知道,攻击者不修改你的代码,而是利用操作系统的调度特性,在极短的时间窗口内篡改文件系统状态。这里就有个概念:即“检查时间/使用时间竞态条件”(TOCTOU, Time Of Check To Time Of Use)。
因此,
防御的核心是消除竞态条件,确保“检查”和“使用”是原子操作。
三、TOCTOU 竞态条件漏洞
1、背景:符号链接遍历攻击
符号链接(symlink)是一种特殊的文件,它指向另一个文件或目录。攻击者可能通过构造恶意符号链接,诱骗程序访问非预期的文件(如 /etc/passwd
)。为了防止这种攻击,程序通常会先检查路径是否包含符号链接,例如:
cleaned, err := filepath.EvalSymlinks(unsafePath) // 解析路径中的符号链接
if err != nil {return err
}
if !filepath.IsLocal(cleaned) { // 检查路径是否在安全范围内return errors.New("unsafe path")
}
这段代码的逻辑是:
-
解析路径中的所有符号链接,得到最终路径(
cleaned
)。 -
检查路径是否是“本地的”(例如,不包含
..
或绝对路径等危险操作)。
2. TOCTOU 竞态条件漏洞
即使程序在打开文件前检查了路径的安全性,攻击者仍可能在检查(Check)和实际使用(Use)之间篡改路径。例如:
-
攻击步骤:
-
程序检查路径
a/b/c
,确认它是合法的。 -
在检查之后、打开文件之前,攻击者将
a/b/c
替换为指向/etc/passwd
的符号链接。 -
程序实际调用
os.Open(cleaned)
时,会跟随符号链接,意外访问敏感文件。
-
-
代码示例的漏洞:
f, err := os.Open(cleaned) // 攻击者可能在检查后修改路径!
3. 另一种变体:目录移动攻击
另一种 TOCTOU 攻击涉及移动路径中的目录。例如:
-
攻击者提供路径
a/b/c/../../etc/passwd
:-
正常解析后,路径应为
a/etc/passwd
(因为c/../../
会回退两级)。
-
-
攻击时机:
-
在程序解析路径时,攻击者将
a/b/c
重命名为a/b
。 -
此时路径实际指向
a/b/../../etc/passwd
,即/etc/passwd
,导致越权访问。
-
4. 问题的核心
-
根本原因:文件系统的状态在检查和使用之间可能被篡改(竞态条件)。
-
防御难点:传统的“先检查后使用”模式无法保证原子性(检查和操作不是连续的)。
四、防御方法
-
原子性操作:
-
使用
openat
+O_NOFOLLOW
(Linux)或类似机制,确保检查和打开是连续的。 -
示例(Go 中可用
os.OpenFile
设置标志位):-
f, err := os.OpenFile(cleaned, os.O_RDONLY|syscall.O_NOFOLLOW, 0)
-
-
-
降低时间窗口:
-
尽量减少检查和打开之间的代码(减少被攻击的窗口)。
-
-
文件描述符传递:
-
通过已打开的父目录文件描述符操作子路径(避免路径解析竞态)。
-
-
沙盒/权限控制:
-
在容器或低权限环境中运行程序。
-